diff options
114 files changed, 999 insertions, 681 deletions
@@ -61,7 +61,7 @@ gem "bootsnap", ">= 1.1.0", require: false # Active Job. group :job do gem "resque", require: false - gem "resque-scheduler", require: false + gem "resque-scheduler", github: "jeremy/resque-scheduler", branch: "redis-rb-4.0", require: false gem "sidekiq", require: false gem "sucker_punch", require: false gem "delayed_job", require: false @@ -82,7 +82,10 @@ group :cable do gem "em-hiredis", require: false gem "hiredis", require: false - gem "redis", require: false + gem "redis", "~> 4.0", require: false + + # For Redis 4.0 support. Unreleased 9cb81bf. + gem "redis-namespace", github: "resque/redis-namespace" gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 938b4a71cc..9c3a7e3c82 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,6 +7,17 @@ GIT pg (>= 0.17, < 0.20) GIT + remote: https://github.com/jeremy/resque-scheduler.git + revision: 7cccf7d8db4dbbf54bf549a98b6cbb38106dbed2 + branch: redis-rb-4.0 + specs: + resque-scheduler (4.3.0) + mono_logger (~> 1.0) + redis (>= 3.3, < 5) + resque (~> 1.26) + rufus-scheduler (~> 3.2) + +GIT remote: https://github.com/matthewd/rb-inotify.git revision: 856730aad4b285969e8dd621e44808a7c5af4242 branch: close-handling @@ -42,6 +53,13 @@ GIT tilt (>= 1.1, < 3) GIT + remote: https://github.com/resque/redis-namespace.git + revision: 1403f511f6ae1ec9a8f330298a4cacf73fb10afc + specs: + redis-namespace (1.5.3) + redis (>= 3.0.4) + +GIT remote: https://github.com/robin850/sdoc.git revision: 0e340352f3ab2f196c8a8743f83c2ee286e4f71c branch: upgrade @@ -361,9 +379,7 @@ GEM rb-fsevent (0.10.2) rdoc (5.1.0) redcarpet (3.2.3) - redis (3.3.3) - redis-namespace (1.5.3) - redis (~> 3.0, >= 3.0.4) + redis (4.0.1) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) @@ -374,11 +390,6 @@ GEM redis-namespace (~> 1.3) sinatra (>= 0.9.2) vegas (~> 0.1.2) - resque-scheduler (4.3.0) - mono_logger (~> 1.0) - redis (~> 3.3) - resque (~> 1.26) - rufus-scheduler (~> 3.2) retriable (3.1.1) rubocop (0.49.1) parallel (~> 1.10) @@ -403,11 +414,11 @@ GEM sequel (4.49.0) serverengine (1.5.11) sigdump (~> 0.2.2) - sidekiq (5.0.4) + sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (~> 3.3, >= 3.3.3) + redis (>= 3.3.4, < 5) sigdump (0.2.4) signet (0.7.3) addressable (~> 2.3) @@ -520,9 +531,10 @@ DEPENDENCIES rake (>= 11.1) rb-inotify! redcarpet (~> 3.2.3) - redis + redis (~> 4.0) + redis-namespace! resque - resque-scheduler + resque-scheduler! rubocop (>= 0.47) sass-rails! sdoc! diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 560ee89846..3952887b61 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,7 @@ +* Support redis-rb 4.0. + + *Jeremy Daer* + * Hash long stream identifiers when using PostgreSQL adapter. PostgreSQL has a limit on identifiers length (63 chars, [docs](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS)). diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index faafd6d0a6..bb8d64e27a 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -43,7 +43,7 @@ module ActionCable def remove(data) logger.info "Unsubscribing from channel: #{data['identifier']}" - remove_subscription subscriptions[data["identifier"]] + remove_subscription find(data) end def remove_subscription(subscription) diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb index 82fed81a18..26209537df 100644 --- a/actioncable/lib/action_cable/server/configuration.rb +++ b/actioncable/lib/action_cable/server/configuration.rb @@ -25,13 +25,26 @@ module ActionCable # Also makes sure proper dependencies are required. def pubsub_adapter adapter = (cable.fetch("adapter") { "redis" }) + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. path_to_adapter = "action_cable/subscription_adapter/#{adapter}" begin require path_to_adapter - rescue Gem::LoadError => e - raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)." rescue LoadError => e - raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/cable.yml is valid. If you use an adapter other than 'postgresql' or 'redis' add the necessary adapter gem to the Gemfile.", e.backtrace + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end end adapter = adapter.camelize diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb index 07774810ce..1227c793a9 100644 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -3,7 +3,7 @@ require "thread" gem "em-hiredis", "~> 0.3.0" -gem "redis", "~> 3.0" +gem "redis", ">= 3", "< 5" require "em-hiredis" require "redis" diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index facea944ff..c28951608f 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -2,7 +2,7 @@ require "thread" -gem "redis", "~> 3.0" +gem "redis", ">= 3", "< 5" require "redis" module ActionCable @@ -76,7 +76,7 @@ module ActionCable def listen(conn) conn.without_reconnect do - original_client = conn.client + original_client = conn.respond_to?(:_client) ? conn._client : conn.client conn.subscribe("_action_cable_internal") do |on| on.subscribe do |chan, count| diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index fe60f8a6a1..49afec9a12 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -3,7 +3,7 @@ assert_enqueued_email_with ContactMailer, :welcome do ContactMailer.welcome.deliver_later end - + *Mikkel Malmberg* * Allow Action Mailer classes to configure their delivery job. diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 16090e7946..adb86aad9f 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,11 @@ +* Add ability to enable Early Hints for HTTP/2 + + If supported by the server, and enabled in Puma this allows H2 Early Hints to be used. + + The `javascript_include_tag` and the `stylesheet_link_tag` automatically add Early Hints if requested. + + *Eileen M. Uchitelle*, *Aaron Patterson* + * Simplify cookies middleware with key rotation support Use the `rotate` method for both `MessageEncryptor` and diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 3761054bb7..14afb355ab 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -180,8 +180,6 @@ module AbstractController # # ==== Parameters # * <tt>name</tt> - The name of an action to be tested - # - # :api: private def action_method?(name) self.class.action_methods.include?(name) end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 41898c4c2e..7e156586b9 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -20,7 +20,6 @@ module AbstractController # Normalizes arguments, options and then delegates render_to_body and # sticks the result in <tt>self.response_body</tt>. - # :api: public def render(*args, &block) options = _normalize_render(*args, &block) rendered_body = render_to_body(options) @@ -42,19 +41,16 @@ module AbstractController # (as ActionController extends it to be anything that # responds to the method each), this method needs to be # overridden in order to still return a string. - # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end # Performs the actual template rendering. - # :api: public def render_to_body(options = {}) end - # Returns Content-Type of rendered content - # :api: public + # Returns Content-Type of rendered content. def rendered_format Mime[:text] end @@ -65,7 +61,6 @@ module AbstractController # This method should return a hash with assigns. # You can overwrite this configuration per controller. - # :api: public def view_assigns protected_vars = _protected_ivars variables = instance_variables @@ -76,10 +71,10 @@ module AbstractController } end + private # Normalize args by converting <tt>render "foo"</tt> to # <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to # <tt>render :file => "foo/bar"</tt>. - # :api: plugin def _normalize_args(action = nil, options = {}) if action.respond_to?(:permitted?) if action.permitted? @@ -95,20 +90,17 @@ module AbstractController end # Normalize options. - # :api: plugin def _normalize_options(options) options end # Process extra options. - # :api: plugin def _process_options(options) options end # Process the rendered format. - # :api: private - def _process_format(format) + def _process_format(format) # :nodoc: end def _process_variant(options) @@ -121,8 +113,7 @@ module AbstractController end # Normalize args and options. - # :api: private - def _normalize_render(*args, &block) + def _normalize_render(*args, &block) # :nodoc: options = _normalize_args(*args, &block) _process_variant(options) _normalize_options(options) diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 476f0843b2..5ef83af07a 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -83,15 +83,12 @@ module ActionController # def cleanup_view_runtime # super - time_taken_in_something_expensive # end - # - # :api: plugin def cleanup_view_runtime yield end # Every time after an action is processed, this method is invoked # with the payload, so you can add more information. - # :api: plugin def append_info_to_payload(payload) payload[:view_runtime] = view_runtime end @@ -100,7 +97,6 @@ module ActionController # A hook which allows other frameworks to log what happened during # controller process action. This method should return an array # with the messages to be added. - # :api: plugin def log_process_action(payload) #:nodoc: messages, view_runtime = [], payload[:view_runtime] messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 5cd8568d8d..8de57f9199 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -79,15 +79,18 @@ module ActionController # redirect_back fallback_location: "/images/screenshot.jpg" # redirect_back fallback_location: posts_url # redirect_back fallback_location: proc { edit_post_url(@post) } + # redirect_back fallback_location: '/', allow_other_host: false # - # All options that can be passed to <tt>redirect_to</tt> are accepted as + # ==== Options + # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing `Referer` header. + # * <tt>:allow_other_host</tt> - Allows or disallow redirection to the host that is different to the current host + # + # All other options that can be passed to <tt>redirect_to</tt> are accepted as # options and the behavior is identical. - def redirect_back(fallback_location:, **args) - if referer = request.headers["Referer"] - redirect_to referer, **args - else - redirect_to fallback_location, **args - end + def redirect_back(fallback_location:, allow_other_host: true, **args) + referer = request.headers["Referer"] + redirect_to_referer = referer && (allow_other_host || _url_host_allowed?(referer)) + redirect_to redirect_to_referer ? referer : fallback_location, **args end def _compute_redirect_to_location(request, options) #:nodoc: @@ -120,5 +123,11 @@ module ActionController 302 end end + + def _url_host_allowed?(url) + URI(url.to_s).host == request.host + rescue ArgumentError, URI::Error + false + end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index dee7be184a..5c172aecad 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -199,6 +199,23 @@ module ActionDispatch @headers ||= Http::Headers.new(self) end + # Early Hints is an HTTP/2 status code that indicates hints to help a client start + # making preparations for processing the final response. + # + # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers. + # + # The +send_early_hints+ method accepts an hash of links as follows: + # + # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") + # + # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the + # Early Hints headers are included by default if supported. + def send_early_hints(links) + return unless env["rack.early_hints"] + + env["rack.early_hints"].call(links) + end + # Returns a +String+ with the last requested path including their params. # # # get '/foo' diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 45290b6ac3..ef633aadc6 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -1,47 +1,52 @@ # frozen_string_literal: true module ActionDispatch - # This middleware is added to the stack when `config.force_ssl = true`, and is passed - # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP + # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed + # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP # requests: # - # 1. TLS redirect: Permanently redirects http:// requests to https:// - # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options` - # to modify the destination URL - # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set - # `redirect: false` to disable this feature. + # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+ + # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+ + # to modify the destination URL + # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set + # <tt>redirect: false</tt> to disable this feature. # - # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they - # mustn't be sent along with http:// requests. Enabled by default. Set - # `config.ssl_options` with `secure_cookies: false` to disable this feature. + # Requests can opt-out of redirection with +exclude+: # - # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember - # this site as TLS-only and automatically redirect non-TLS requests. - # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable. + # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } # - # Set `config.ssl_options` with `hsts: { … }` to configure HSTS: - # * `expires`: How long, in seconds, these settings will stick. The minimum - # required to qualify for browser preload lists is `18.weeks`. Defaults to - # `180.days` (recommended). - # * `subdomains`: Set to `true` to tell the browser to apply these settings - # to all subdomains. This protects your cookies from interception by a - # vulnerable site on a subdomain. Defaults to `true`. - # * `preload`: Advertise that this site may be included in browsers' - # preloaded HSTS lists. HSTS protects your site on every visit *except the - # first visit* since it hasn't seen your HSTS header yet. To close this - # gap, browser vendors include a baked-in list of HSTS-enabled sites. - # Go to https://hstspreload.appspot.com to submit your site for inclusion. - # Defaults to `false`. + # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they + # must not be sent along with +http://+ requests. Enabled by default. Set + # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature. # - # To turn off HSTS, omitting the header is not enough. Browsers will remember the - # original HSTS directive until it expires. Instead, use the header to tell browsers to - # expire HSTS immediately. Setting `hsts: false` is a shortcut for - # `hsts: { expires: 0 }`. + # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember + # this site as TLS-only and automatically redirect non-TLS requests. + # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable. # - # Requests can opt-out of redirection with `exclude`: + # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS: # - # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } + # * +expires+: How long, in seconds, these settings will stick. The minimum + # required to qualify for browser preload lists is 18 weeks. Defaults to + # 180 days (recommended). + # + # * +subdomains+: Set to +true+ to tell the browser to apply these settings + # to all subdomains. This protects your cookies from interception by a + # vulnerable site on a subdomain. Defaults to +true+. + # + # * +preload+: Advertise that this site may be included in browsers' + # preloaded HSTS lists. HSTS protects your site on every visit <i>except the + # first visit</i> since it hasn't seen your HSTS header yet. To close this + # gap, browser vendors include a baked-in list of HSTS-enabled sites. + # Go to https://hstspreload.org to submit your site for inclusion. + # Defaults to +false+. + # + # To turn off HSTS, omitting the header is not enough. Browsers will remember the + # original HSTS directive until it expires. Instead, use the header to tell browsers to + # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for + # <tt>hsts: { expires: 0 }</tt>. class SSL + # :stopdoc: + # Default to 180 days, the low end for https://www.ssllabs.com/ssltest/ # and greater than the 18-week requirement for browser preload lists. HSTS_EXPIRES_IN = 15552000 diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb index ef680cafed..d64be3b3d9 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb @@ -14,7 +14,7 @@ module ActionDispatch def method_missing(method, *args, &block) if METHODS.include?(method) - raise NoMethodError + raise NoMethodError, "System tests cannot make direct requests via ##{method}; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information." else super end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index ae1f368e8b..8caa71199e 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -12,38 +12,38 @@ require_relative "request_encoder" module ActionDispatch module Integration #:nodoc: module RequestHelpers - # Performs a GET request with the given parameters. See +#process+ for more - # details. + # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def get(path, **args) process(:get, path, **args) end - # Performs a POST request with the given parameters. See +#process+ for more - # details. + # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def post(path, **args) process(:post, path, **args) end - # Performs a PATCH request with the given parameters. See +#process+ for more - # details. + # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def patch(path, **args) process(:patch, path, **args) end - # Performs a PUT request with the given parameters. See +#process+ for more - # details. + # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def put(path, **args) process(:put, path, **args) end - # Performs a DELETE request with the given parameters. See +#process+ for - # more details. + # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def delete(path, **args) process(:delete, path, **args) end - # Performs a HEAD request with the given parameters. See +#process+ for more - # details. + # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def head(path, *args) process(:head, path, *args) end diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index d9f200b2a7..280134f8d2 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -47,7 +47,6 @@ module Dispatching end class BaseTest < Rack::TestCase - # :api: plugin test "simple dispatching" do get "/dispatching/simple/index" @@ -56,14 +55,12 @@ module Dispatching assert_content_type "text/plain; charset=utf-8" end - # :api: plugin test "directly modifying response body" do get "/dispatching/simple/modify_response_body" assert_body "success" end - # :api: plugin test "directly modifying response body twice" do get "/dispatching/simple/modify_response_body_twice" diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index e447b66486..2959dc3e4d 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -62,6 +62,10 @@ class RedirectController < ActionController::Base redirect_back(fallback_location: "/things/stuff", status: 307) end + def safe_redirect_back_with_status + redirect_back(fallback_location: "/things/stuff", status: 307, allow_other_host: false) + end + def host_redirect redirect_to action: "other_host", only_path: false, host: "other.test.host" end @@ -259,6 +263,23 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/things/stuff", 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 + + assert_response 307 + assert_equal "http://test.host/things/stuff", redirect_to_url + end + + def test_safe_redirect_back_from_the_same_host + referer = "http://test.host/coming/from" + @request.env["HTTP_REFERER"] = referer + get :safe_redirect_back_with_status + + assert_response 307 + assert_equal referer, redirect_to_url + end + def test_redirect_to_record with_routing do |set| set.draw do diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 2051f7546f..40cbad3b0d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -917,6 +917,25 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end + def test_rotating_signed_cookies_digest + @request.env["action_dispatch.signed_cookie_digest"] = "SHA256" + @request.env["action_dispatch.cookies_rotations"].rotate :signed, digest: "SHA1" + + key_generator = @request.env["action_dispatch.key_generator"] + + old_secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + old_value = ActiveSupport::MessageVerifier.new(old_secret).generate(45) + + @request.headers["Cookie"] = "user_id=#{old_value}" + get :get_signed_cookie + + assert_equal 45, @controller.send(:cookies).signed[:user_id] + + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256") + assert_equal 45, verifier.verify(@response.cookies["user_id"]) + end + def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie key_generator = @request.env["action_dispatch.key_generator"] encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 68c6d26364..2a18395aac 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1304,3 +1304,18 @@ class RequestFormData < BaseRequestTest assert !request.form_data? end end + +class EarlyHintsRequestTest < BaseRequestTest + def setup + super + @env["rack.early_hints"] = lambda { |links| links } + @request = stub_request + end + + test "when early hints is set in the env link headers are sent" do + early_hints = @request.send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") + expected_hints = { "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload" } + + assert_equal expected_hints, early_hints + end +end diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb index b60ec559ae..771a2a8d6e 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -36,32 +36,37 @@ end class UndefMethodsTest < DrivenBySeleniumWithChrome test "get" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do get "http://example.com" end + assert_equal "System tests cannot make direct requests via #get; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "post" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do post "http://example.com" end + assert_equal "System tests cannot make direct requests via #post; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "put" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do put "http://example.com" end + assert_equal "System tests cannot make direct requests via #put; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "patch" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do patch "http://example.com" end + assert_equal "System tests cannot make direct requests via #patch; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "delete" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do delete "http://example.com" end + assert_equal "System tests cannot make direct requests via #delete; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end end diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md index f321b9f720..8198011b02 100644 --- a/actionview/app/assets/javascripts/README.md +++ b/actionview/app/assets/javascripts/README.md @@ -1,5 +1,4 @@ -Ruby on Rails unobtrusive scripting adapter. -======================================== +# Ruby on Rails unobtrusive scripting adapter This unobtrusive scripting support file is developed for the Ruby on Rails framework, but is not strictly tied to any specific backend. You can drop this into any application to: @@ -8,51 +7,47 @@ This unobtrusive scripting support file is developed for the Ruby on Rails frame - make forms or hyperlinks submit data asynchronously with Ajax; - have submit buttons become automatically disabled on form submit to prevent double-clicking. -These features are achieved by adding certain ["data" attributes][data] to your HTML markup. In Rails, they are added by the framework's template helpers. +These features are achieved by adding certain [`data` attributes][data] to your HTML markup. In Rails, they are added by the framework's template helpers. -Requirements ------------- +## Optional prerequisites -- HTML5 doctype (optional). +Note that the `data` attributes this library adds are a feature of HTML5. If you're not targeting HTML5, these attributes may make your HTML to fail [validation][validator]. However, this shouldn't create any issues for web browsers or other user agents. -If you don't use HTML5, adding "data" attributes to your HTML4 or XHTML pages might make them fail [W3C markup validation][validator]. However, this shouldn't create any issues for web browsers or other user agents. +## Installation -Installation using npm ------------- +### NPM -Run `npm install rails-ujs --save` to install the rails-ujs package. + npm install rails-ujs --save + +### Yarn + + yarn add rails-ujs -Installation using Yarn ------------- +## Usage -Run `yarn add rails-ujs` to install the rails-ujs package. +### Asset pipeline -Usage ------------- - -Require `rails-ujs` in your application.js manifest. +In a conventional Rails application that uses the asset pipeline, require `rails-ujs` in your `application.js` manifest: ```javascript //= require rails-ujs ``` -Usage with yarn ------------- +### ES2015+ -When using with the Webpacker gem or your preferred JavaScript bundler, just -add the following to your main JS file and compile. +If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file: ```javascript import Rails from 'rails-ujs'; Rails.start() ``` -How to run tests ------------- +## How to run tests Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser. ## License + rails-ujs is released under the [MIT License](MIT-LICENSE). [data]: http://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes "Embedding custom non-visible data with the data-* attributes" diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb index e1b02fbde4..665a9e3171 100644 --- a/actionview/lib/action_view/context.rb +++ b/actionview/lib/action_view/context.rb @@ -19,7 +19,6 @@ module ActionView attr_accessor :output_buffer, :view_flow # Prepares the context by setting the appropriate instance variables. - # :api: plugin def _prepare_context @view_flow = OutputFlow.new @output_buffer = nil @@ -29,7 +28,6 @@ module ActionView # Encapsulates the interaction with the view flow so it # returns the correct buffer on +yield+. This is usually # overwritten by helpers to add more behavior. - # :api: plugin def _layout_for(name = nil) name ||= :layout view_flow.get(name).html_safe diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index bc2713d13e..10b366b030 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -37,6 +37,9 @@ module ActionView # When the Asset Pipeline is enabled, you can pass the name of your manifest as # source, and include other JavaScript or CoffeeScript files inside the manifest. # + # If the server supports Early Hints header links for these assets will be + # automatically pushed. + # # ==== Options # # When the last parameter is a hash you can add HTML attributes using that @@ -77,12 +80,20 @@ module ActionView def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys - sources.uniq.map { |source| + early_hints_links = [] + + sources_tags = sources.uniq.map { |source| + href = path_to_javascript(source, path_options) + early_hints_links << "<#{href}>; rel=preload; as=script" tag_options = { - "src" => path_to_javascript(source, path_options) + "src" => href }.merge!(options) content_tag("script".freeze, "", tag_options) }.join("\n").html_safe + + request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) + + sources_tags end # Returns a stylesheet link tag for the sources specified as arguments. If @@ -92,6 +103,9 @@ module ActionView # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to # apply to all media types. # + # If the server supports Early Hints header links for these assets will be + # automatically pushed. + # # stylesheet_link_tag "style" # # => <link href="/assets/style.css" media="screen" rel="stylesheet" /> # @@ -113,14 +127,22 @@ module ActionView def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys - sources.uniq.map { |source| + early_hints_links = [] + + sources_tags = sources.uniq.map { |source| + href = path_to_stylesheet(source, path_options) + early_hints_links << "<#{href}>; rel=preload; as=stylesheet" tag_options = { "rel" => "stylesheet", "media" => "screen", - "href" => path_to_stylesheet(source, path_options) + "href" => href }.merge!(options) tag(:link, tag_options) }.join("\n").html_safe + + request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) + + sources_tags end # Returns a link tag that browsers and feed readers can use to auto-detect diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 31a1f8be8c..8a08e49e2f 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -394,7 +394,7 @@ module ActionView # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" /> # # radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true - # # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." /> + # # => <input disabled="disabled" id="time_slot_3:00_p.m." name="time_slot" type="radio" value="3:00 p.m." /> # # radio_button_tag 'color', "green", true, class: "color_input" # # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" /> diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index a64d7e396e..a6cec3f69c 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -166,7 +166,7 @@ module ActionView # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> # from 1.4.3. # - # tag.div data: { city_state: %w( Chigaco IL ) } + # tag.div data: { city_state: %w( Chicago IL ) } # # => <div data-city-state="["Chicago","IL"]"></div> # # The generated attributes are escaped by default. This can be disabled using diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 2648f9153f..62b5047f56 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -75,8 +75,7 @@ module ActionView end # Returns an object that is able to render templates. - # :api: private - def view_renderer + def view_renderer # :nodoc: @_view_renderer ||= ActionView::Renderer.new(lookup_context) end @@ -92,7 +91,6 @@ module ActionView private # Find and render a template based on the options given. - # :api: private def _render_template(options) variant = options.delete(:variant) assigns = options.delete(:assigns) @@ -114,7 +112,6 @@ module ActionView # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :template => "foo/bar". - # :api: private def _normalize_args(action = nil, options = {}) options = super(action, options) case action @@ -137,7 +134,6 @@ module ActionView end # Normalize options. - # :api: private def _normalize_options(options) options = super(options) if options[:partial] == true diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 93be2be2d1..e1cbae5845 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -270,7 +270,7 @@ module ActionView begin routes = @controller.respond_to?(:_routes) && @controller._routes rescue - # Dont call routes, if there is an error on _routes call + # Don't call routes, if there is an error on _routes call end if routes && @@ -286,7 +286,7 @@ module ActionView begin routes = @controller.respond_to?(:_routes) && @controller._routes rescue - # Dont call routes, if there is an error on _routes call + # Don't call routes, if there is an error on _routes call end routes && diff --git a/actionview/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb index 367d2c3174..3a698fa42e 100644 --- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb @@ -17,21 +17,11 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base render partial: Reply.base end - def render_with_has_many_through_association - @developer = Developer.first - render partial: @developer.topics - end - def render_with_has_one_association @company = Company.find(1) render partial: @company.mascot end - def render_with_belongs_to_association - @reply = Reply.find(1) - render partial: @reply.topic - end - def render_with_record @developer = Developer.first render partial: @developer diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 6645839f0e..7475f5cc3c 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -19,6 +19,7 @@ class AssetTagHelperTest < ActionView::TestCase def ssl?() false end def host_with_port() "localhost" end def base_url() "http://www.example.com" end + def send_early_hints(links) end end.new @controller.request = @request @@ -305,6 +306,24 @@ class AssetTagHelperTest < ActionView::TestCase %(font_path("font.ttf?123")) => %(/fonts/font.ttf?123) } + FontUrlToTag = { + %(font_url("font.eot")) => %(http://www.example.com/fonts/font.eot), + %(font_url("font.eot#iefix")) => %(http://www.example.com/fonts/font.eot#iefix), + %(font_url("font.woff")) => %(http://www.example.com/fonts/font.woff), + %(font_url("font.ttf")) => %(http://www.example.com/fonts/font.ttf), + %(font_url("font.ttf?123")) => %(http://www.example.com/fonts/font.ttf?123), + %(font_url("font.ttf", host: "http://assets.example.com")) => %(http://assets.example.com/fonts/font.ttf) + } + + UrlToFontToTag = { + %(url_to_font("font.eot")) => %(http://www.example.com/fonts/font.eot), + %(url_to_font("font.eot#iefix")) => %(http://www.example.com/fonts/font.eot#iefix), + %(url_to_font("font.woff")) => %(http://www.example.com/fonts/font.woff), + %(url_to_font("font.ttf")) => %(http://www.example.com/fonts/font.ttf), + %(url_to_font("font.ttf?123")) => %(http://www.example.com/fonts/font.ttf?123), + %(url_to_font("font.ttf", host: "http://assets.example.com")) => %(http://assets.example.com/fonts/font.ttf) + } + def test_autodiscovery_link_tag_with_unknown_type_but_not_pass_type_option_key assert_raise(ArgumentError) do auto_discovery_link_tag(:xml) @@ -547,6 +566,14 @@ class AssetTagHelperTest < ActionView::TestCase FontPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_font_url + FontUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_font_alias_for_font_url + UrlToFontToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_video_audio_tag_does_not_modify_options options = { autoplay: true } video_tag("video", options) @@ -627,7 +654,9 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase @controller = BasicController.new @controller.config.relative_url_root = "/collaboration/hieraki" - @request = Struct.new(:protocol, :base_url).new("gopher://", "gopher://www.example.com") + @request = Struct.new(:protocol, :base_url) do + def send_early_hints(links); end + end.new("gopher://", "gopher://www.example.com") @controller.request = @request end diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb index 4478c9f4ab..a72bc6c2fe 100644 --- a/actionview/test/template/javascript_helper_test.rb +++ b/actionview/test/template/javascript_helper_test.rb @@ -6,11 +6,15 @@ class JavaScriptHelperTest < ActionView::TestCase tests ActionView::Helpers::JavaScriptHelper attr_accessor :output_buffer + attr_reader :request setup do @old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json ActiveSupport.escape_html_entities_in_json = true @template = self + @request = Class.new do + def send_early_hints(links) end + end.new end def teardown diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 77dfdefc05..425ab0c789 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,7 @@ +* Support redis-rb 4.0. + + *Jeremy Daer* + * Change logging instrumentation to log errors when a job raises an exception. Fixes #26848. diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb index 484b476567..7d5174b957 100644 --- a/activejob/test/support/integration/adapters/resque.rb +++ b/activejob/test/support/integration/adapters/resque.rb @@ -3,7 +3,7 @@ module ResqueJobsManager def setup ActiveJob::Base.queue_adapter = :resque - Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.connect(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true) + Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.new(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true) Resque.logger = Rails.logger unless can_run? puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n" @@ -41,11 +41,8 @@ module ResqueJobsManager end def can_run? - begin - Resque.redis.client.connect - rescue - return false - end - true + Resque.redis.ping == "PONG" + rescue + false end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f73e27b91f..288f22a9d3 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,22 +1,22 @@ -* PostgreSQL `tsrange` now preserves subsecond precision +* PostgreSQL `tsrange` now preserves subsecond precision. PostgreSQL 9.1+ introduced range types, and Rails added support for using - this datatype in ActiveRecord. However, the serialization of - PostgreSQL::OID::Range was incomplete, because it did not properly + this datatype in Active Record. However, the serialization of + `PostgreSQL::OID::Range` was incomplete, because it did not properly cast the bounds that make up the range. This led to subseconds being dropped in SQL commands: - (byebug) from = type_cast_single_for_database(range.first) - 2010-01-01 13:30:00 UTC + Before: - (byebug) to = type_cast_single_for_database(range.last) - 2011-02-02 19:30:00 UTC + connection.type_cast(tsrange.serialize(range_value)) + # => "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)" - (byebug) "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}" - "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)" + Now: - (byebug) "[#{type_cast(from)},#{type_cast(to)}#{value.exclude_end? ? ')' : ']'}" - "['2010-01-01 13:30:00.670277','2011-02-02 19:30:00.745125')" + connection.type_cast(tsrange.serialize(range_value)) + # => "[2010-01-01 13:30:00.670277,2011-02-02 19:30:00.745125)" + + *Thomas Cannon* * Passing a `Set` to `Relation#where` now behaves the same as passing an array. diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 096f016976..a8d8ee268a 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -6,22 +6,16 @@ module ActiveRecord module Associations # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency class AliasTracker # :nodoc: - def self.create(connection, initial_table) - aliases = Hash.new(0) - aliases[initial_table] = 1 - new(connection, aliases) - end - - def self.create_with_joins(connection, initial_table, joins) + def self.create(connection, initial_table, joins) if joins.empty? - create(connection, initial_table) + aliases = Hash.new(0) else aliases = Hash.new { |h, k| h[k] = initial_count_for(connection, k, joins) } - aliases[initial_table] = 1 - new(connection, aliases) end + aliases[initial_table] = 1 + new(connection, aliases) end def self.initial_count_for(connection, name, table_joins) @@ -36,6 +30,8 @@ module ActiveRecord ).size elsif join.respond_to? :left join.left.table_name == name ? 1 : 0 + elsif join.is_a?(Hash) + join[name] else # this branch is reached by two tests: # @@ -79,10 +75,7 @@ module ActiveRecord end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :aliases + attr_reader :aliases private diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index b2dc044312..11967e0571 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -23,8 +23,7 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.create(klass.connection, klass.table_name) - chain = get_chain(reflection, association, alias_tracker) + chain = get_chain(reflection, association, scope.alias_tracker) scope.extending! reflection.extensions add_constraints(scope, owner, chain) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index ceedf150e3..ed215fb22c 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -181,8 +181,6 @@ module ActiveRecord # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) - return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, options[:dependent]) end @@ -192,8 +190,6 @@ module ActiveRecord # Note that this method removes records from the database ignoring the # +:dependent+ option. def destroy(*records) - return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, :destroy) end @@ -376,6 +372,8 @@ module ActiveRecord end def delete_or_destroy(records, method) + return if records.empty? + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } records = records.flatten records.each { |record| raise_on_type_mismatch!(record) } existing_records = records.reject(&:new_record?) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 77b3d11b47..df4bf07999 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -43,8 +43,6 @@ module ActiveRecord Column = Struct.new(:name, :alias) end - attr_reader :alias_tracker, :base_klass, :join_root - def self.make_tree(associations) hash = {} walk_tree associations, hash @@ -90,8 +88,8 @@ module ActiveRecord # associations # => [:appointments] # joins # => [] # - def initialize(base, table, associations, joins, eager_loading: true) - @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins) + def initialize(base, table, associations, alias_tracker, eager_loading: true) + @alias_tracker = alias_tracker @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new(base, table, build(tree, base)) @@ -158,6 +156,9 @@ module ActiveRecord parents.values end + protected + attr_reader :alias_tracker, :base_klass, :join_root + private def make_constraints(parent, child, tables, join_type) @@ -224,7 +225,7 @@ module ActiveRecord raise EagerLoadPolymorphicError.new(reflection) end - JoinAssociation.new reflection, build(right, reflection.klass) + JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker) end.compact end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index a526468bf6..a007d0cf5f 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -11,11 +11,12 @@ module ActiveRecord attr_accessor :tables - def initialize(reflection, children) + def initialize(reflection, children, alias_tracker) super(reflection.klass, children) - @reflection = reflection - @tables = nil + @alias_tracker = alias_tracker + @reflection = reflection + @tables = nil end def match?(other) @@ -38,11 +39,12 @@ module ActiveRecord joins << table.create_join(table, table.create_on(constraint), join_type) join_scope = reflection.join_scope(table, foreign_klass) + arel = join_scope.arel(alias_tracker.aliases) - if join_scope.arel.constraints.any? - joins.concat join_scope.arel.join_sources + if arel.constraints.any? + joins.concat arel.join_sources right = joins.last.right - right.expr = right.expr.and(join_scope.arel.constraints) + right.expr = right.expr.and(arel.constraints) end # The current table in this iteration becomes the foreign table in the next @@ -55,6 +57,9 @@ module ActiveRecord def table tables.first end + + protected + attr_reader :alias_tracker end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index e4ca6c8408..891e556dc4 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -33,7 +33,9 @@ module ActiveRecord BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) - class GeneratedAttributeMethods < Module; end # :nodoc: + class GeneratedAttributeMethods < Module #:nodoc: + include Mutex_m + end module ClassMethods def inherited(child_class) #:nodoc: @@ -42,7 +44,7 @@ module ActiveRecord end def initialize_generated_modules # :nodoc: - @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m } + @generated_attribute_methods = GeneratedAttributeMethods.new @attribute_methods_generated = false include @generated_attribute_methods diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index a2439e6ec7..ac1070e65b 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -240,7 +240,7 @@ module ActiveRecord # # private # - # def log_chidren + # def log_children # # Child processing # end # @@ -265,7 +265,7 @@ module ActiveRecord # # private # - # def log_chidren + # def log_children # # Child processing # 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 4f0c1890be..e6bcdbda60 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1174,7 +1174,7 @@ module ActiveRecord end # Changes the comment for a column or removes it if +nil+. - def change_column_comment(table_name, column_name, comment) #:nodoc: + def change_column_comment(table_name, column_name, comment) raise NotImplementedError, "#{self.class} does not support changing column comments" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 8c889f98f5..6859feb2f3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -478,6 +478,8 @@ module ActiveRecord m.alias_type %r(number)i, "decimal" m.alias_type %r(double)i, "float" + m.register_type %r(^json)i, Type::Json.new + m.register_type(%r(decimal)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index ae991d3d79..add6f8632b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -563,7 +563,6 @@ module ActiveRecord m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, Type::Json.new register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 29542f917e..508132accb 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -183,13 +183,25 @@ module ActiveRecord raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter" begin require path_to_adapter - rescue Gem::LoadError => e - raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)." rescue LoadError => e - raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end end adapter_method = "#{spec[:adapter]}_connection" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4d37a292d6..5b6ad5fbc4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -312,14 +312,14 @@ module ActiveRecord def get_advisory_lock(lock_id) # :nodoc: unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 - raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") end query_value("SELECT pg_try_advisory_lock(#{lock_id})") end def release_advisory_lock(lock_id) # :nodoc: unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 - raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") end query_value("SELECT pg_advisory_unlock(#{lock_id})") end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 502cef2e20..2b247f7a7a 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -52,11 +52,8 @@ module ActiveRecord end if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -65,11 +62,8 @@ module ActiveRecord def change_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -80,11 +74,8 @@ module ActiveRecord column_options.reverse_merge!(type: :integer) if block_given? - super(table_1, table_2, column_options: column_options, **options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -103,6 +94,14 @@ module ActiveRecord super(table_name, ref_name, type: :integer, **options) end alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end end class V4_2 < V5_0 @@ -121,11 +120,8 @@ module ActiveRecord def create_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -134,11 +130,8 @@ module ActiveRecord def change_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -174,6 +167,12 @@ module ActiveRecord end private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 3517091a6e..997cfe4b5e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -551,6 +551,11 @@ module ActiveRecord limit_value || offset_value end + def alias_tracker(joins = [], aliases = nil) # :nodoc: + joins += [aliases] if aliases + ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins) + end + protected def load_records(records) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0889d61c92..4ef0502893 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -130,7 +130,7 @@ module ActiveRecord # end def calculate(operation, column_name) if has_include?(column_name) - relation = construct_relation_for_association_calculations + relation = apply_join_dependency(construct_join_dependency) relation.distinct! if operation.to_s.downcase == "count" relation.calculate(operation, column_name) @@ -180,7 +180,8 @@ module ActiveRecord end if has_include?(column_names.first) - construct_relation_for_association_calculations.pluck(*column_names) + relation = apply_join_dependency(construct_join_dependency) + relation.pluck(*column_names) else relation = spawn relation.select_values = column_names.map { |cn| diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c92d5a52f4..707245bab2 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -310,12 +310,12 @@ module ActiveRecord return false if !conditions || limit_value == 0 - relation = self unless eager_loading? - relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false)) - - return false if ActiveRecord::NullRelation === relation + if eager_loading? + relation = apply_join_dependency(construct_join_dependency(eager_loading: false)) + return relation.exists?(conditions) + end - relation = construct_relation_for_exists(relation, conditions) + relation = construct_relation_for_exists(conditions) skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false rescue ::RangeError @@ -366,17 +366,16 @@ module ActiveRecord # preexisting join in joins_values to categorizations (by way of # the `has_many :through` for categories). # - join_dependency = construct_join_dependency(joins_values) + join_dependency = construct_join_dependency - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) + relation = apply_join_dependency(join_dependency) + relation._select!(join_dependency.aliases.columns) yield relation, join_dependency end - def construct_relation_for_exists(relation, conditions) - relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + def construct_relation_for_exists(conditions) + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) case conditions when Array, Hash @@ -388,17 +387,15 @@ module ActiveRecord relation end - def construct_join_dependency(joins = [], eager_loading: true) + def construct_join_dependency(eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(klass, table, including, joins, eager_loading: eager_loading) - end - - def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(joins_values)) + ActiveRecord::Associations::JoinDependency.new( + klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading + ) end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency) + def apply_join_dependency(join_dependency) + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) relation diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 03824ffff9..ebc72d28fd 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -122,7 +122,7 @@ module ActiveRecord end join_dependency = ActiveRecord::Associations::JoinDependency.new( - other.klass, other.table, joins_dependency, [] + other.klass, other.table, joins_dependency, other.alias_tracker ) relation.joins! rest diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c88603fde2..578d8a6eb7 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -441,7 +441,7 @@ module ActiveRecord # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" # def left_outer_joins(*args) - check_if_method_has_arguments!(:left_outer_joins, args) + check_if_method_has_arguments!(__callee__, args) args.compact! args.flatten! @@ -898,8 +898,8 @@ module ActiveRecord end # Returns the Arel object associated with the relation. - def arel # :nodoc: - @arel ||= build_arel + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) end protected @@ -921,11 +921,11 @@ module ActiveRecord raise ImmutableRelation if defined?(@arel) && @arel end - def build_arel + def build_arel(aliases) arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten) unless joins_values.empty? - build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? + build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? @@ -970,7 +970,7 @@ module ActiveRecord end end - def build_left_outer_joins(manager, outer_joins) + def build_left_outer_joins(manager, outer_joins, aliases) buckets = outer_joins.group_by do |join| case join when Hash, Symbol, Array @@ -980,10 +980,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::OuterJoin) + build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases) end - def build_joins(manager, joins) + def build_joins(manager, joins, aliases) buckets = joins.group_by do |join| case join when String @@ -999,10 +999,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases) end - def build_join_query(manager, buckets, join_type) + def build_join_query(manager, buckets, join_type, aliases) buckets.default = [] association_joins = buckets[:association_join] @@ -1013,7 +1013,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, join_list + klass, table, association_joins, alias_tracker(join_list, aliases) ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb new file mode 100644 index 0000000000..568a524058 --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "cases/helper" +require "cases/json_shared_test_cases" + +class SQLite3JSONTest < ActiveRecord::SQLite3TestCase + include JSONSharedTestCases + + def setup + super + @connection.create_table("json_data_type") do |t| + t.column "payload", :json, default: {} + t.column "settings", :json + end + end + + def test_default + @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } + klass.reset_column_information + + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"]) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions) + end + + private + def column_type + :json + end +end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 521b388cee..c6a4ac356f 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1250,6 +1250,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase TenantMembership.current_member = nil end + def test_has_many_trough_with_scope_that_has_joined_same_table_with_parent_relation + assert_equal authors(:david), Author.joins(:comments_for_first_author).take + end + def test_has_many_through_with_unscope_should_affect_to_through_scope assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 2d67c57cfb..2f42684212 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -999,6 +999,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal ["title"], model.accessed_fields end + test "generated attribute methods ancestors have correct class" do + mod = Topic.send(:generated_attribute_methods) + assert_match %r(GeneratedAttributeMethods), mod.inspect + end + private def new_topic_like_ar_class(&block) diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 3fa0ca8366..5b80f16a44 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -19,7 +19,7 @@ module ActiveRecord spec "ridiculous://foo?encoding=utf8" end - assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message + assert_match "Could not load the 'ridiculous' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", error.message end # The abstract adapter is used simply to bypass the bit of code that diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 55496147c1..d8bc917e7f 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -9,6 +9,7 @@ require "models/company" require "models/tagging" require "models/topic" require "models/reply" +require "models/rating" require "models/entrant" require "models/project" require "models/developer" @@ -156,6 +157,32 @@ class FinderTest < ActiveRecord::TestCase assert_raise(NoMethodError) { Topic.exists?([1, 2]) } end + def test_exists_with_scope + davids = Author.where(name: "David") + assert_equal true, davids.exists? + assert_equal true, davids.exists?(authors(:david).id) + assert_equal false, davids.exists?(authors(:mary).id) + assert_equal false, davids.exists?("42") + assert_equal false, davids.exists?(42) + assert_equal false, davids.exists?(davids.new.id) + + fake = Author.where(name: "fake author") + assert_equal false, fake.exists? + assert_equal false, fake.exists?(authors(:david).id) + end + + def test_exists_uses_existing_scope + post = authors(:david).posts.first + authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) + assert_equal true, authors.exists?(authors(:david).id) + end + + def test_any_with_scope_on_hash_includes + post = authors(:david).posts.first + categories = Categorization.includes(author: :posts).where(posts: { id: post.id }) + assert_equal true, categories.exists? + end + def test_exists_with_polymorphic_relation post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")]) relation = Post.tagged_with_comment("tagging comment") @@ -244,6 +271,13 @@ class FinderTest < ActiveRecord::TestCase assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists? end + def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association + assert_nothing_raised do + developer = developers(:david) + developer.ratings.includes(comment: :post).where(posts: { id: 1 }).exists? + end + end + def test_exists_with_empty_table_and_no_args_given Topic.delete_all assert_equal false, Topic.exists? diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb index 56ec8c8a82..a71485982c 100644 --- a/activerecord/test/cases/json_shared_test_cases.rb +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -30,6 +30,7 @@ module JSONSharedTestCases end def test_change_table_supports_json + skip unless @connection.supports_json? @connection.change_table("json_data_type") do |t| t.public_send column_type, "users" end @@ -40,6 +41,7 @@ module JSONSharedTestCases end def test_schema_dumping + skip unless @connection.supports_json? output = dump_table_schema("json_data_type") assert_match(/t\.#{column_type}\s+"settings"/, output) end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 4edaf79e9a..72433d1e8e 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -19,12 +19,12 @@ require "models/tyre" require "models/minivan" require "models/possession" require "models/reader" +require "models/category" require "models/categorization" require "models/edge" class RelationTest < ActiveRecord::TestCase - fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, - :tags, :taggings, :cars, :minivans + fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans class TopicWithCallbacks < ActiveRecord::Base self.table_name = :topics @@ -845,32 +845,6 @@ class RelationTest < ActiveRecord::TestCase } end - def test_exists - davids = Author.where(name: "David") - assert davids.exists? - assert davids.exists?(authors(:david).id) - assert ! davids.exists?(authors(:mary).id) - assert ! davids.exists?("42") - assert ! davids.exists?(42) - assert ! davids.exists?(davids.new.id) - - fake = Author.where(name: "fake author") - assert ! fake.exists? - assert ! fake.exists?(authors(:david).id) - end - - def test_exists_uses_existing_scope - post = authors(:david).posts.first - authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) - assert authors.exists?(authors(:david).id) - end - - def test_any_with_scope_on_hash_includes - post = authors(:david).posts.first - categories = Categorization.includes(author: :posts).where(posts: { id: post.id }) - assert categories.exists? - end - def test_last authors = Author.all assert_equal authors(:bob), authors.last @@ -1810,6 +1784,10 @@ class RelationTest < ActiveRecord::TestCase assert_equal [posts(:welcome)], custom_post_relation.ranked_by_comments.limit_by(1).to_a end + test "alias_tracker respects a custom table" do + assert_equal posts(:welcome), custom_post_relation("categories_posts").joins(:categories).first + end + test "#load" do relation = Post.all assert_queries(1) do @@ -1930,8 +1908,8 @@ class RelationTest < ActiveRecord::TestCase end private - def custom_post_relation - table_alias = Post.arel_table.alias("omg_posts") + def custom_post_relation(alias_name = "omg_posts") + table_alias = Post.arel_table.alias(alias_name) table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 3371fcbfcc..e9eba9be2e 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -22,6 +22,7 @@ class Author < ActiveRecord::Base has_many :comments_containing_the_letter_e, through: :posts, source: :comments has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments has_many :comments_with_include, -> { includes(:post).where(posts: { type: "Post" }) }, through: :posts, source: :comments + has_many :comments_for_first_author, -> { for_first_author }, through: :posts, source: :comments has_many :first_posts has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index 7477b09d09..84b8f3827b 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -15,6 +15,7 @@ # If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one. class ActiveStorage::Blob < ActiveRecord::Base class UnpreviewableError < StandardError; end + class UnrepresentableError < StandardError; end self.table_name = "active_storage_blobs" @@ -23,6 +24,8 @@ class ActiveStorage::Blob < ActiveRecord::Base class_attribute :service + has_many :attachments + has_one_attached :preview_image class << self @@ -121,7 +124,7 @@ class ActiveStorage::Blob < ActiveRecord::Base # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController # can then produce on-demand. def variant(transformations) - ActiveStorage::Variant.new(self, ActiveStorage::Variation.new(transformations)) + ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations)) end @@ -141,7 +144,7 @@ class ActiveStorage::Blob < ActiveRecord::Base # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?. def preview(transformations) if previewable? - ActiveStorage::Preview.new(self, ActiveStorage::Variation.new(transformations)) + ActiveStorage::Preview.new(self, ActiveStorage::Variation.wrap(transformations)) else raise UnpreviewableError end @@ -153,6 +156,31 @@ class ActiveStorage::Blob < ActiveRecord::Base end + # Returns an ActiveStorage::Preview instance for a previewable blob or an ActiveStorage::Variant instance for an image blob. + # + # blob.representation(resize: "100x100").processed.service_url + # + # Raises ActiveStorage::Blob::UnrepresentableError if the receiving blob is neither an image nor previewable. Call + # ActiveStorage::Blob#representable? to determine whether a blob is representable. + # + # See ActiveStorage::Blob#preview and ActiveStorage::Blob#variant for more information. + def representation(transformations) + case + when previewable? + preview transformations + when image? + variant transformations + else + raise UnrepresentableError + end + end + + # Returns true if the blob is an image or is previewable. + def representable? + image? || previewable? + end + + # Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL. # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And diff --git a/activestorage/app/models/active_storage/preview.rb b/activestorage/app/models/active_storage/preview.rb index 42c4bbc5a4..be5053edae 100644 --- a/activestorage/app/models/active_storage/preview.rb +++ b/activestorage/app/models/active_storage/preview.rb @@ -23,8 +23,8 @@ # # The built-in previewers rely on third-party system libraries: # -# * {ffmpeg}[https://www.ffmpeg.org] -# * {mupdf}[https://mupdf.com] +# * {ffmpeg}[https://www.ffmpeg.org] +# * {mupdf}[https://mupdf.com] # # These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you # install and use third-party software, make sure you understand the licensing implications of doing so. diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb index 54685b4c0e..915b78162c 100644 --- a/activestorage/app/models/active_storage/variant.rb +++ b/activestorage/app/models/active_storage/variant.rb @@ -50,7 +50,7 @@ class ActiveStorage::Variant # Returns a combination key of the blob and the variation that together identifies a specific variant. def key - "variants/#{blob.key}/#{variation.key}" + "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}" end # Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly @@ -65,6 +65,10 @@ class ActiveStorage::Variant service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type end + # Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be duck-typed. + def image + self + end private def processed? diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb index df2643442a..13bad87cac 100644 --- a/activestorage/app/models/active_storage/variation.rb +++ b/activestorage/app/models/active_storage/variation.rb @@ -13,16 +13,21 @@ class ActiveStorage::Variation attr_reader :transformations class << self - def wrap(variation_or_key) - case variation_or_key + # Returns a Variation instance based on the given variator. If the variator is a Variation, it is + # returned unmodified. If it is a String, it is passed to ActiveStorage::Variation.decode. Otherwise, + # it is assumed to be a transformations Hash and is passed directly to the constructor. + def wrap(variator) + case variator when self - variation_or_key + variator + when String + decode variator else - decode variation_or_key + new variator end end - # Returns a variation instance with the transformations that were encoded by +encode+. + # Returns a Variation instance with the transformations that were encoded by +encode+. def decode(key) new ActiveStorage.verifier.verify(key, purpose: :variation) end diff --git a/activestorage/lib/active_storage/previewer.rb b/activestorage/lib/active_storage/previewer.rb index c91f64ac65..930b376067 100644 --- a/activestorage/lib/active_storage/previewer.rb +++ b/activestorage/lib/active_storage/previewer.rb @@ -55,7 +55,7 @@ module ActiveStorage # end def draw(*argv) # :doc: Tempfile.open("output") do |file| - capture *argv, to: file + capture(*argv, to: file) yield file end end diff --git a/activestorage/test/models/preview_test.rb b/activestorage/test/models/preview_test.rb index 317a2d5c58..bcd8442f4b 100644 --- a/activestorage/test/models/preview_test.rb +++ b/activestorage/test/models/preview_test.rb @@ -7,6 +7,7 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase test "previewing a PDF" do blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf") preview = blob.preview(resize: "640x280").processed + assert preview.image.attached? assert_equal "report.png", preview.image.filename.to_s assert_equal "image/png", preview.image.content_type @@ -19,6 +20,7 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase test "previewing an MP4 video" do blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4") preview = blob.preview(resize: "640x280").processed + assert preview.image.attached? assert_equal "video.png", preview.image.filename.to_s assert_equal "image/png", preview.image.content_type diff --git a/activestorage/test/models/representation_test.rb b/activestorage/test/models/representation_test.rb new file mode 100644 index 0000000000..29fe61aee4 --- /dev/null +++ b/activestorage/test/models/representation_test.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::RepresentationTest < ActiveSupport::TestCase + test "representing an image" do + blob = create_file_blob + representation = blob.representation(resize: "100x100").processed + + image = read_image(representation.image) + assert_equal 100, image.width + assert_equal 67, image.height + end + + test "representing a PDF" do + blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf") + representation = blob.representation(resize: "640x280").processed + + image = read_image(representation.image) + assert_equal 612, image.width + assert_equal 792, image.height + end + + test "representing an MP4 video" do + blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4") + representation = blob.representation(resize: "640x280").processed + + image = read_image(representation.image) + assert_equal 640, image.width + assert_equal 480, image.height + end + + test "representing an unrepresentable blob" do + blob = create_blob + + assert_raises ActiveStorage::Blob::UnrepresentableError do + blob.representation resize: "100x100" + end + end +end diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb index d7cbef4e55..b7d20ab55a 100644 --- a/activestorage/test/models/variant_test.rb +++ b/activestorage/test/models/variant_test.rb @@ -26,4 +26,9 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase assert_equal 67, image.height assert_match(/Gray/, image.colorspace) end + + test "service_url doesn't grow in length despite long variant options" do + variant = @blob.variant(font: "a" * 10_000).processed + assert_operator variant.service_url.length, :<, 500 + end end diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb index 4b7e70b8b1..be31bbe858 100644 --- a/activestorage/test/service/azure_storage_service_test.rb +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -13,7 +13,7 @@ if SERVICE_CONFIGURATIONS[:azure] url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png") - assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar.png%22&rsct=image%2Fpng/, url) + assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar\.png%22%3B\+filename\*%3DUTF-8%27%27avatar\.png&rsct=image%2Fpng/, url) assert_match SERVICE_CONFIGURATIONS[:azure][:container], url end end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index c95f95d076..c7924fa9ae 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -61,12 +61,12 @@ Previously: 'one_two'.camelize(true) - => nil + # => nil Now: 'one_two'.camelize(true) - => ArgumentError: Invalid option, use either :upper or :lower. + # => ArgumentError: Invalid option, use either :upper or :lower. *Ricardo DÃaz* @@ -81,12 +81,12 @@ Prior to Rails 5.1: 5.minutes % 2.minutes - => 60 + # => 60 Now: 5.minutes % 2.minutes - => 1 minute + # => 1 minute Fixes #29603 and #29743. diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 28df2c066e..f98d343ccd 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -39,9 +39,8 @@ module ActiveSupport def cleanup(options = nil) options = merged_options(options) search_dir(cache_path) do |fname| - key = file_path_key(fname) - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? + entry = read_entry(fname, options) + delete_entry(fname, options) if entry && entry.expired? end end diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index c48edb135f..0aae073fb5 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -55,10 +55,10 @@ class Time # end # end # - # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt> - # objects that have already been created, e.g. any model timestamp - # attributes that have been read before the block will remain in - # the application's default timezone. + # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt> + # objects that have already been created, e.g. any model timestamp + # attributes that have been read before the block will remain in + # the application's default timezone. def use_zone(time_zone) new_zone = find_zone!(time_zone) begin diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 8a1918039c..0cc4f58453 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -77,7 +77,7 @@ module ActiveSupport # Though if both the secret and the cipher was changed at the same time, # the above should be combined into: # - # verifier.rotate old_secret, cipher: "aes-256-cbc" + # crypt.rotate old_secret, cipher: "aes-256-cbc" class MessageEncryptor prepend Messages::Rotator::Encryptor diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb index 391ab60b3a..66231b0a82 100644 --- a/activesupport/test/cache/stores/file_store_test.rb +++ b/activesupport/test/cache/stores/file_store_test.rb @@ -118,6 +118,7 @@ class FileStoreTest < ActiveSupport::TestCase assert_not @cache.exist?("foo") assert @cache.exist?("baz") assert @cache.exist?("quux") + assert_equal 2, Dir.glob(File.join(cache_dir, "**")).size end end diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb index 53ea9e393f..471faa8c12 100644 --- a/activesupport/test/encrypted_configuration_test.rb +++ b/activesupport/test/encrypted_configuration_test.rb @@ -54,12 +54,4 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase test "raises key error when accessing config via bang method" do assert_raise(KeyError) { @credentials.something! } end - - private - def new_credentials_configuration - ActiveSupport::EncryptedConfiguration.new \ - config_path: @credentials_config_path, - key_path: @credentials_key_path, - env_key: "RAILS_MASTER_KEY" - end end diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index b878ac20fa..41d653fa59 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -406,6 +406,29 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end + def test_indifferent_transform_keys_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.transform_keys! { |k| k * 2 } + + assert_equal({ "aa" => 1, "bb" => 2 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + + def test_indifferent_transform_values + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_values { |v| v * 2 } + + assert_equal({ "a" => 2, "b" => 4 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_transform_values_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.transform_values! { |v| v * 2 } + + assert_equal({ "a" => 2, "b" => 4 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + def test_indifferent_compact hash_contain_nil_value = @strings.merge("z" => nil) hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 3f5a3c7ade..1020f4a8e7 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -231,7 +231,7 @@ Rails chooses between file, template, and action depending on whether there is a ### Application Controller Renamed -If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be application_controller.rb in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process. +If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be `application_controller.rb` in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process. * More Information: * [The Death of Application.rb](http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/) @@ -304,7 +304,7 @@ Rails now keeps a per-request local cache of read from the remote cache stores, Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a `Posts` controller with a `show` action. By default, this will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :da`, it will render `app/views/posts/show.da.html.erb`. If the localized template isn't present, the undecorated version will be used. Rails also includes `I18n#available_locales` and `I18n::SimpleBackend#available_locales`, which return an array of the translations that are available in the current Rails project. -In addition, you can use the same scheme to localize the rescue files in the `public` directory: `public/500.da.html` or `public/404.en.html` work, for example. +In addition, you can use the same scheme to localize the rescue files in the public directory: `public/500.da.html` or `public/404.en.html` work, for example. ### Partial Scoping for Translations diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index 6570b19f97..f6571544f9 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -30,7 +30,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails. ### What to update in your apps -* Update your Gemfile to depend on +* Update your `Gemfile` to depend on * `rails = 3.2.0` * `sass-rails ~> 3.2.3` * `coffee-rails ~> 3.2.1` diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 6f1b75a42b..0921cd1979 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -66,7 +66,7 @@ Major Features * **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store. * **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters. * **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used. -* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems. +* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a `Gemfile` to manage installed gems. ### ActionPack diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index a30bfc458a..036a310ac8 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -179,7 +179,7 @@ change your code to use the explicit form (`render file: "foo/bar"`) instead. `respond_with` and the corresponding class-level `respond_to` have been moved to the [responders](https://github.com/plataformatec/responders) gem. Add -`gem 'responders', '~> 2.0'` to your Gemfile to use it: +`gem 'responders', '~> 2.0'` to your `Gemfile` to use it: ```ruby # app/controllers/users_controller.rb diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 3805fd2a63..656838c6b8 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -775,7 +775,7 @@ Please refer to the [Changelog][active-record] for detailed changes. * Added prepared statements support to `mysql2` adapter, for mysql2 0.4.4+, Previously this was only supported on the deprecated `mysql` legacy adapter. - To enable, set `prepared_statements: true` in config/database.yml. + To enable, set `prepared_statements: true` in `config/database.yml`. ([Pull Request](https://github.com/rails/rails/pull/23461)) * Added ability to call `ActionRecord::Relation#update` on relation objects diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 8afec00018..6959f992aa 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -16,6 +16,7 @@ <% end %> <p> The guides for earlier releases: +<a href="http://guides.rubyonrails.org/v5.1/">Rails 5.1</a>, <a href="http://guides.rubyonrails.org/v5.0/">Rails 5.0</a>, <a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>, <a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>, diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 31151e0329..dd16ba3932 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -557,7 +557,7 @@ The async adapter is intended for development/testing and should not be used in Action Cable contains two Redis adapters: "normal" Redis and Evented Redis. Both of the adapters require users to provide a URL pointing to the Redis server. -Additionally, a channel_prefix may be provided to avoid channel name collisions +Additionally, a `channel_prefix` may be provided to avoid channel name collisions when using the same Redis server for multiple applications. See the [Redis PubSub documentation](https://redis.io/topics/pubsub#database-amp-scoping) for more details. ##### PostgreSQL Adapter diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 5fb8e300de..d53c4dedf9 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -784,9 +784,9 @@ The way this is done is to add a non-guessable token which is only known to your If you generate a form like this: ```erb -<%= form_for @user do |f| %> - <%= f.text_field :username %> - <%= f.text_field :password %> +<%= form_with model: @user, local: true do |form| %> + <%= form.text_field :username %> + <%= form.text_field :password %> <% end %> ``` @@ -1116,7 +1116,7 @@ Rails default exception handling displays a "500 Server Error" message for all e ### The Default 500 and 404 Templates -By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. +By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the public folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. ### `rescue_from` diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index a57623428f..349108c207 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -490,7 +490,7 @@ stylesheet_link_tag "application" # => <link href="/assets/application.css" medi #### stylesheet_path -Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. +Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by `stylesheet_link_tag` to build the stylesheet path. ```ruby stylesheet_path "application" # => /assets/application.css diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index b8f076a27b..ee0472621b 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -464,7 +464,7 @@ a `password` accessor with certain validations on it. #### Requirements `ActiveModel::SecurePassword` depends on [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), -so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly. +so include this gem in your `Gemfile` to use `ActiveModel::SecurePassword` correctly. In order to make this work, the model must have an accessor named `password_digest`. The `has_secure_password` will add the following validations on the `password` accessor: diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 3573c3c77b..3786343fc3 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1712,10 +1712,10 @@ Client.find_by_sql("SELECT * FROM clients ### `select_all` -`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record. +`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. This method will return an instance of `ActiveRecord::Result` class and calling `to_hash` on this object would return you an array of hashes where each hash indicates a record. ```ruby -Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") +Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_hash # => [ # {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, # {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index ae573cc77c..067a7b7cb6 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1752,7 +1752,7 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr "2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200 ``` -Default is `:utc`. +Default is `:local`. Please refer to the documentation of `Date._parse` for further details. diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index ff4288a7f5..3b31557f41 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -450,6 +450,53 @@ Active Job | `:adapter` | QueueAdapter object processing the job | | `:job` | Job object | +Active Storage +-------------- + +### service_upload.active_storage + +| Key | Value | +| ------------ | ---------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:checksum` | Checksum to ensure integrity | + +### service_streaming_download.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_download.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_delete.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_exist.active_storage + +| Key | Value | +| ------------ | --------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:exist` | File or blob exists or not | + +### service_url.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:url` | Generated url | Railties -------- @@ -549,4 +596,4 @@ end ``` You should follow Rails conventions when defining your own events. The format is: `event.library`. -If you application is sending Tweets, you should create an event named `tweet.twitter`. +If your application is sending Tweets, you should create an event named `tweet.twitter`. diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 8bd1f91304..805b0f0d62 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -65,7 +65,7 @@ config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included -in the Gemfile and no `config.assets.css_compressor` option is set. +in the `Gemfile` and no `config.assets.css_compressor` option is set. ### Main Features @@ -181,7 +181,7 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates -the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the +the file `scaffolds.css` (or `scaffolds.scss` if `sass-rails` is in the `Gemfile`.) For example, if you generate a `ProjectsController`, Rails will also add a new @@ -202,7 +202,7 @@ will result in your assets being included more than once. WARNING: When using asset precompilation, you will need to ensure that your controller assets will be precompiled when loading them on a per page basis. By -default .coffee and .scss files will not be precompiled on their own. See +default `.coffee` and `.scss` files will not be precompiled on their own. See [Precompiling Assets](#precompiling-assets) for more information on how precompiling works. @@ -726,7 +726,7 @@ include, you can add them to the `precompile` array in `config/initializers/asse Rails.application.config.assets.precompile += %w( admin.js admin.css ) ``` -NOTE. Always specify an expected compiled filename that ends with .js or .css, +NOTE. Always specify an expected compiled filename that ends with `.js` or `.css`, even if you want to add Sass or CoffeeScript files to the precompile array. The task also generates a `.sprockets-manifest-md5hash.json` (where `md5hash` is @@ -1090,7 +1090,7 @@ Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. -The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). +The default `Gemfile` includes [uglifier](https://github.com/lautis/uglifier). This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space and comments, shortening local variable names, and performing other micro-optimizations such @@ -1219,7 +1219,7 @@ Sprockets uses Processors, Transformers, Compressors, and Exporters to extend Sprockets functionality. Have a look at [Extending Sprockets](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md) to learn more. Here we registered a preprocessor to add a comment to the end -of text/css (.css) files. +of text/css (`.css`) files. ```ruby module AddComment diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 2cd8e02a77..88c559921c 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -659,6 +659,6 @@ development: ... ``` -It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. +It also generated some lines in our `database.yml` configuration corresponding to our choice of PostgreSQL for database. NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the `rails new` command to generate the basis of your app. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 0f87d73d6e..7a32607eb7 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -391,7 +391,7 @@ by setting up a Rake task which runs ``` for all models and all boolean columns, after which the flag must be set to true -by adding the following to your application.rb file: +by adding the following to your `application.rb` file: ```ruby Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true @@ -572,7 +572,7 @@ Defaults to `'signed cookie'`. error should be raised for missing translations. * `config.action_view.automatically_disable_submit_tag` determines whether - submit_tag should automatically disable on click, this defaults to `true`. + `submit_tag` should automatically disable on click, this defaults to `true`. * `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`. @@ -1317,7 +1317,7 @@ know which pages it is allowed to index. Rails creates this file for you inside the `/public` folder. By default, it allows search engines to index all pages of your application. If you want to block -indexing on all pages of you application, use this: +indexing on all pages of your application, use this: ``` User-agent: * diff --git a/guides/source/engines.md b/guides/source/engines.md index 188620a683..9f39832a7e 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -537,12 +537,12 @@ directory at `app/views/blorgh/comments` and in it a new file called ```html+erb <h3>New comment</h3> -<%= form_for [@article, @article.comments.build] do |f| %> +<%= form_with(model: [@article, @article.comments.build], local: true) do |form| %> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> - <%= f.submit %> + <%= form.submit %> <% end %> ``` @@ -653,7 +653,7 @@ there isn't an application handy to test this out in, generate one using the $ rails new unicorn ``` -Usually, specifying the engine inside the Gemfile would be done by specifying it +Usually, specifying the engine inside the `Gemfile` would be done by specifying it as a normal, everyday gem. ```ruby @@ -783,8 +783,8 @@ added above the `title` field with this code: ```html+erb <div class="field"> - <%= f.label :author_name %><br> - <%= f.text_field :author_name %> + <%= form.label :author_name %><br> + <%= form.text_field :author_name %> </div> ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 70a945ad9e..b007baea87 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -172,14 +172,14 @@ of the files and folders that Rails created by default: |app/|Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You'll focus on this folder for the remainder of this guide.| |bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.| |config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| -|config.ru|Rack configuration for Rack based servers used to start the application.| +|config.ru|Rack configuration for Rack based servers used to start the application. For more information about Rack, see the [Rack website](https://rack.github.io/).| |db/|Contains your current database schema, as well as the database migrations.| |Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](https://bundler.io).| |lib/|Extended modules for your application.| |log/|Application log files.| |package.json|This file allows you to specify what npm dependencies are needed for your Rails application. This file is used by Yarn. For more information about Yarn, see the [Yarn website](https://yarnpkg.com/lang/en/).| |public/|The only folder seen by the world as-is. Contains static files and compiled assets.| -|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the `lib/tasks` directory of your application.| +|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing `Rakefile`, you should add your own tasks by adding files to the `lib/tasks` directory of your application.| |README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| |tmp/|Temporary files (like cache and pid files).| @@ -372,16 +372,17 @@ singular form `article` and makes meaningful use of the distinction. ```bash $ bin/rails routes - Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index + Prefix Verb URI Pattern Controller#Action +welcome_index GET /welcome/index(.:format) welcome#index + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new + edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index ``` In the next section, you will add the ability to create new articles in your @@ -567,15 +568,16 @@ To see what Rails will do with this, we look back at the output of ```bash $ bin/rails routes Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index +welcome_index GET /welcome/index(.:format) welcome#index + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new + edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index ``` The `articles_path` helper tells Rails to point the form to the URI Pattern diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index fe2477f2ae..b9b327252f 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -232,14 +232,14 @@ You can send an HTML string back to the browser by using the `:html` option to `render`: ```ruby -render html: "<strong>Not Found</strong>".html_safe +render html: helpers.tag.strong('Not Found') ``` TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method. +NOTE: When using `html:` option, HTML entities will be escaped if the string is not composed with `html_safe`-aware APIs. #### Rendering JSON diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 5048444cb2..15073af6be 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -446,7 +446,7 @@ Publishing Your Gem ------------------- Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply -commit the code to a Git repository (like GitHub) and add a line to the Gemfile of the application in question: +commit the code to a Git repository (like GitHub) and add a line to the `Gemfile` of the application in question: ```ruby gem "yaffle", git: "https://github.com/rails/yaffle.git" diff --git a/guides/source/security.md b/guides/source/security.md index a07d583f15..cfa777d433 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -169,11 +169,12 @@ you would first assign the new configuration value: Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256" ``` -Then you'd set up a rotation with the old configuration to keep it alive. +Now add a rotation for the old SHA1 digest so existing cookies are +seamlessly upgraded to the new SHA256 digest. ```ruby Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies| - cookies.rotate :signed, digest: "SHA256" + cookies.rotate :signed, digest: "SHA1" end ``` @@ -535,7 +536,7 @@ Depending on your web application, there may be more ways to hijack the user's a INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect registration forms from attackers and comment forms from automatic spam bots by asking the user to type the letters of a distorted image. This is the positive CAPTCHA, but there is also the negative CAPTCHA. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._ -A popular positive CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API. +A popular positive CAPTCHA API is [reCAPTCHA](https://developers.google.com/recaptcha/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API. You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails. The problem with CAPTCHAs is that they have a negative impact on the user experience. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. Still, positive CAPTCHAs are one of the best methods to prevent all kinds of bots from submitting forms. diff --git a/guides/source/testing.md b/guides/source/testing.md index 4ee3267261..c5b2a694e7 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1513,7 +1513,7 @@ class BillingJobTest < ActiveJob::TestCase end ``` -This test is pretty simple and only asserts that the job get the work done +This test is pretty simple and only asserts that the job got the work done as expected. By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index d932fc8d8f..9bc87e4bf0 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -27,7 +27,7 @@ The process should go as follows: 3. Fix tests and deprecated features. 4. Move to the latest patch version of the next minor version. -Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. +Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the `Gemfile` (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. You can find a list of all released Rails versions [here](https://rubygems.org/gems/rails/versions). @@ -411,7 +411,7 @@ Upgrading from Rails 4.1 to Rails 4.2 ### Web Console -First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. +First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your `Gemfile` and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. ### Responders @@ -1136,7 +1136,7 @@ full support for the last few changes in the specification. ### Gemfile Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that -line from your Gemfile when upgrading. You should also update your application +line from your `Gemfile` when upgrading. You should also update your application file (in `config/application.rb`): ```ruby diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index ff440b7939..20603dedaf 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Gemfile for new apps: upgrade redis-rb from ~> 3.0 to 4.0. + + *Jeremy Daer* + * Add `mini_magick` to default `Gemfile` as comment. *Yoshiyuki Hirano* diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 785265d766..5b5037d3de 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -127,6 +127,7 @@ module Rails class_option "dev-caching", aliases: "-C", type: :boolean, default: nil, desc: "Specifies whether to perform caching in development." class_option "restart", type: :boolean, default: nil, hide: true + class_option "early_hints", type: :boolean, default: nil, desc: "Enables HTTP/2 early hints." def initialize(args = [], local_options = {}, config = {}) @original_options = local_options @@ -161,7 +162,8 @@ module Rails daemonize: options[:daemon], pid: pid, caching: options["dev-caching"], - restart_cmd: restart_command + restart_cmd: restart_command, + early_hints: early_hints } end end @@ -227,6 +229,10 @@ module Rails "bin/rails server #{@server} #{@original_options.join(" ")} --restart" end + def early_hints + options[:early_hints] + end + def pid File.expand_path(options[:pid]) end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c8688fb7f3..6fb4ea52b3 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -369,7 +369,7 @@ module Rails return [] if options[:skip_action_cable] comment = "Use Redis adapter to run Action Cable in production" gems = [] - gems << GemfileEntry.new("redis", "~> 3.0", comment, {}, true) + gems << GemfileEntry.new("redis", "~> 4.0", comment, {}, true) gems end diff --git a/railties/test/application/dbconsole_test.rb b/railties/test/application/dbconsole_test.rb index ba04c81b4d..8eb293c179 100644 --- a/railties/test/application/dbconsole_test.rb +++ b/railties/test/application/dbconsole_test.rb @@ -19,21 +19,19 @@ module ApplicationTests end def test_use_value_defined_in_environment_file_in_database_yml - Dir.chdir(app_path) do - app_file "config/database.yml", <<-YAML - development: - database: <%= Rails.application.config.database %> - adapter: sqlite3 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - timeout: 5000 - YAML + app_file "config/database.yml", <<-YAML + development: + database: <%= Rails.application.config.database %> + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + YAML - app_file "config/environments/development.rb", <<-RUBY - Rails.application.configure do - config.database = "db/development.sqlite3" - end - RUBY - end + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY master, slave = PTY.open spawn_dbconsole(slave) @@ -43,22 +41,20 @@ module ApplicationTests end def test_respect_environment_option - Dir.chdir(app_path) do - app_file "config/database.yml", <<-YAML - default: &default - adapter: sqlite3 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - timeout: 5000 + app_file "config/database.yml", <<-YAML + default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 - development: - <<: *default - database: db/development.sqlite3 + development: + <<: *default + database: db/development.sqlite3 - production: - <<: *default - database: db/production.sqlite3 - YAML - end + production: + <<: *default + database: db/production.sqlite3 + YAML master, slave = PTY.open spawn_dbconsole(slave, "-e production") diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 11c886e991..de1e240fd3 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -300,7 +300,7 @@ class LoadingTest < ActiveSupport::TestCase end MIGRATION - Dir.chdir(app_path) { `rake db:migrate` } + rails("db:migrate") require "#{rails_root}/config/environment" get "/title" @@ -314,7 +314,7 @@ class LoadingTest < ActiveSupport::TestCase end MIGRATION - Dir.chdir(app_path) { `rake db:migrate` } + rails("db:migrate") get "/body" assert_equal "BODY", last_response.body diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 391676aa31..b7b6ed8a0e 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -83,10 +83,8 @@ module ApplicationTests end test "db:drop failure because database does not exist" do - Dir.chdir(app_path) do - output = rails("db:drop:_unsafe", "--trace") - assert_match(/does not exist/, output) - end + output = rails("db:drop:_unsafe", "--trace") + assert_match(/does not exist/, output) end test "db:drop failure because bad permissions" do @@ -100,13 +98,11 @@ module ApplicationTests end def db_migrate_and_status(expected_database) - Dir.chdir(app_path) do - rails "generate", "model", "book", "title:string" - rails "db:migrate" - output = rails("db:migrate:status") - assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) - assert_match(/up\s+\d{14}\s+Create books/, output) - end + rails "generate", "model", "book", "title:string" + rails "db:migrate" + output = rails("db:migrate:status") + assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) + assert_match(/up\s+\d{14}\s+Create books/, output) end test "db:migrate and db:migrate:status without database_url" do @@ -161,12 +157,11 @@ module ApplicationTests test "db:fixtures:load with namespaced fixture" do require "#{app_path}/config/environment" - Dir.chdir(app_path) do - rails "generate", "model", "admin::book", "title:string" - rails "db:migrate", "db:fixtures:load" - require "#{app_path}/app/models/admin/book" - assert_equal 2, Admin::Book.count - end + + rails "generate", "model", "admin::book", "title:string" + rails "db:migrate", "db:fixtures:load" + require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count end def db_structure_dump_and_load(expected_database) @@ -205,56 +200,52 @@ module ApplicationTests end test "db:schema:load and db:structure:load do not purge the existing database" do - Dir.chdir(app_path) do - rails "runner", "ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }" + rails "runner", "ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }" - app_file "db/schema.rb", <<-RUBY - ActiveRecord::Schema.define(version: 20140423102712) do - create_table(:comments) {} - end - RUBY + app_file "db/schema.rb", <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:comments) {} + end + RUBY - list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip } + list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip } - assert_equal '["posts"]', list_tables[] - rails "db:schema:load" - assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] + assert_equal '["posts"]', list_tables[] + rails "db:schema:load" + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] - app_file "db/structure.sql", <<-SQL - CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); - SQL + app_file "db/structure.sql", <<-SQL + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + SQL - rails "db:structure:load" - assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[] - end + rails "db:structure:load" + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[] end test "db:schema:load with inflections" do - Dir.chdir(app_path) do - app_file "config/initializers/inflection.rb", <<-RUBY - ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular 'goose', 'geese' - end - RUBY - app_file "config/initializers/primary_key_table_name.rb", <<-RUBY - ActiveRecord::Base.primary_key_prefix_type = :table_name - RUBY - app_file "db/schema.rb", <<-RUBY - ActiveRecord::Schema.define(version: 20140423102712) do - create_table("goose".pluralize) do |t| - t.string :name - end + app_file "config/initializers/inflection.rb", <<-RUBY + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'goose', 'geese' + end + RUBY + app_file "config/initializers/primary_key_table_name.rb", <<-RUBY + ActiveRecord::Base.primary_key_prefix_type = :table_name + RUBY + app_file "db/schema.rb", <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table("goose".pluralize) do |t| + t.string :name end - RUBY + end + RUBY - rails "db:schema:load" + rails "db:schema:load" - tables = rails("runner", "p ActiveRecord::Base.connection.tables").strip - assert_match(/"geese"/, tables) + tables = rails("runner", "p ActiveRecord::Base.connection.tables").strip + assert_match(/"geese"/, tables) - columns = rails("runner", "p ActiveRecord::Base.connection.columns('geese').map(&:name)").strip - assert_equal columns, '["gooseid", "name"]' - end + columns = rails("runner", "p ActiveRecord::Base.connection.columns('geese').map(&:name)").strip + assert_equal columns, '["gooseid", "name"]' end test "db:schema:load fails if schema.rb doesn't exist yet" do @@ -300,10 +291,8 @@ module ApplicationTests puts ActiveRecord::Base.connection_config[:database] RUBY - Dir.chdir(app_path) do - database_path = rails("db:setup") - assert_equal "development.sqlite3", File.basename(database_path.strip) - end + database_path = rails("db:setup") + assert_equal "development.sqlite3", File.basename(database_path.strip) ensure ENV["RAILS_ENV"] = @old_rails_env ENV["RACK_ENV"] = @old_rack_env diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb index b0d51eb22e..33f2b7038c 100644 --- a/railties/test/application/rake/migrations_test.rb +++ b/railties/test/application/rake/migrations_test.rb @@ -15,69 +15,63 @@ module ApplicationTests end test "running migrations with given scope" do - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "model", "user", "username:string", "password:string" - app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION - class AMigration < ActiveRecord::Migration::Current - end - MIGRATION + app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION + class AMigration < ActiveRecord::Migration::Current + end + MIGRATION - output = rails("db:migrate", "SCOPE=bukkits") - assert_no_match(/create_table\(:users\)/, output) - assert_no_match(/CreateUsers/, output) - assert_no_match(/add_column\(:users, :email, :string\)/, output) + output = rails("db:migrate", "SCOPE=bukkits") + assert_no_match(/create_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/add_column\(:users, :email, :string\)/, output) - assert_match(/AMigration: migrated/, output) + assert_match(/AMigration: migrated/, output) - output = rails("db:migrate", "SCOPE=bukkits", "VERSION=0") - assert_no_match(/drop_table\(:users\)/, output) - assert_no_match(/CreateUsers/, output) - assert_no_match(/remove_column\(:users, :email\)/, output) + output = rails("db:migrate", "SCOPE=bukkits", "VERSION=0") + assert_no_match(/drop_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/remove_column\(:users, :email\)/, output) - assert_match(/AMigration: reverted/, output) - end + assert_match(/AMigration: reverted/, output) end test "migration with empty version" do - Dir.chdir(app_path) do - output = rails("db:migrate", "VERSION=", allow_failure: true) - assert_match(/Empty VERSION provided/, output) + output = rails("db:migrate", "VERSION=", allow_failure: true) + assert_match(/Empty VERSION provided/, output) - output = rails("db:migrate:redo", "VERSION=", allow_failure: true) - assert_match(/Empty VERSION provided/, output) + output = rails("db:migrate:redo", "VERSION=", allow_failure: true) + assert_match(/Empty VERSION provided/, output) - output = rails("db:migrate:up", "VERSION=", allow_failure: true) - assert_match(/VERSION is required/, output) + output = rails("db:migrate:up", "VERSION=", allow_failure: true) + assert_match(/VERSION is required/, output) - output = rails("db:migrate:up", allow_failure: true) - assert_match(/VERSION is required/, output) + output = rails("db:migrate:up", allow_failure: true) + assert_match(/VERSION is required/, output) - output = rails("db:migrate:down", "VERSION=", allow_failure: true) - assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) + output = rails("db:migrate:down", "VERSION=", allow_failure: true) + assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) - output = rails("db:migrate:down", allow_failure: true) - assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) - end + output = rails("db:migrate:down", allow_failure: true) + assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) end test "model and migration generator with change syntax" do - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" - rails "generate", "migration", "add_email_to_users", "email:string" - - output = rails("db:migrate") - assert_match(/create_table\(:users\)/, output) - assert_match(/CreateUsers: migrated/, output) - assert_match(/add_column\(:users, :email, :string\)/, output) - assert_match(/AddEmailToUsers: migrated/, output) - - output = rails("db:rollback", "STEP=2") - assert_match(/drop_table\(:users\)/, output) - assert_match(/CreateUsers: reverted/, output) - assert_match(/remove_column\(:users, :email, :string\)/, output) - assert_match(/AddEmailToUsers: reverted/, output) - end + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + + output = rails("db:migrate") + assert_match(/create_table\(:users\)/, output) + assert_match(/CreateUsers: migrated/, output) + assert_match(/add_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: migrated/, output) + + output = rails("db:rollback", "STEP=2") + assert_match(/drop_table\(:users\)/, output) + assert_match(/CreateUsers: reverted/, output) + assert_match(/remove_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: reverted/, output) end test "migration status when schema migrations table is not present" do @@ -86,93 +80,85 @@ module ApplicationTests end test "migration status" do - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" - rails "generate", "migration", "add_email_to_users", "email:string" - rails "db:migrate" + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = rails("db:migrate:status") + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) - rails "db:rollback", "STEP=1" - output = rails("db:migrate:status") + rails "db:rollback", "STEP=1" + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/down\s+\d{14}\s+Add email to users/, output) - end + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) end test "migration status without timestamps" do add_to_config("config.active_record.timestamped_migrations = false") - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" - rails "generate", "migration", "add_email_to_users", "email:string" - rails "db:migrate" + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = rails("db:migrate:status") + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) - rails "db:rollback", "STEP=1" - output = rails("db:migrate:status") + rails "db:rollback", "STEP=1" + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/down\s+\d{3,}\s+Add email to users/, output) - end + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) end test "migration status after rollback and redo" do - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" - rails "generate", "migration", "add_email_to_users", "email:string" - rails "db:migrate" + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = rails("db:migrate:status") + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) - rails "db:rollback", "STEP=2" - output = rails("db:migrate:status") + rails "db:rollback", "STEP=2" + output = rails("db:migrate:status") - assert_match(/down\s+\d{14}\s+Create users/, output) - assert_match(/down\s+\d{14}\s+Add email to users/, output) + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) - rails "db:migrate:redo" - output = rails("db:migrate:status") + rails "db:migrate:redo" + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) - end + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) end test "migration status after rollback and forward" do - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" - rails "generate", "migration", "add_email_to_users", "email:string" - rails "db:migrate" + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = rails("db:migrate:status") + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) - rails "db:rollback", "STEP=2" - output = rails("db:migrate:status") + rails "db:rollback", "STEP=2" + output = rails("db:migrate:status") - assert_match(/down\s+\d{14}\s+Create users/, output) - assert_match(/down\s+\d{14}\s+Add email to users/, output) + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) - rails "db:forward", "STEP=2" - output = rails("db:migrate:status") + rails "db:forward", "STEP=2" + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) - end + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) end test "raise error on any move when current migration does not exist" do @@ -209,50 +195,45 @@ module ApplicationTests test "migration status after rollback and redo without timestamps" do add_to_config("config.active_record.timestamped_migrations = false") - Dir.chdir(app_path) do - rails "generate", "model", "user", "username:string", "password:string" - rails "generate", "migration", "add_email_to_users", "email:string" - rails "db:migrate" + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = rails("db:migrate:status") + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) - rails "db:rollback", "STEP=2" - output = rails("db:migrate:status") + rails "db:rollback", "STEP=2" + output = rails("db:migrate:status") - assert_match(/down\s+\d{3,}\s+Create users/, output) - assert_match(/down\s+\d{3,}\s+Add email to users/, output) + assert_match(/down\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) - rails "db:migrate:redo" - output = rails("db:migrate:status") + rails "db:migrate:redo" + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) - end + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) end test "running migrations with not timestamp head migration files" do - Dir.chdir(app_path) do + app_file "db/migrate/1_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration::Current + end + MIGRATION - app_file "db/migrate/1_one_migration.rb", <<-MIGRATION - class OneMigration < ActiveRecord::Migration::Current - end - MIGRATION + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION - app_file "db/migrate/02_two_migration.rb", <<-MIGRATION - class TwoMigration < ActiveRecord::Migration::Current - end - MIGRATION + rails "db:migrate" - rails "db:migrate" + output = rails("db:migrate:status") - output = rails("db:migrate:status") - - assert_match(/up\s+001\s+One migration/, output) - assert_match(/up\s+002\s+Two migration/, output) - end + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) end test "schema generation when dump_schema_after_migration is set" do diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 76bc0ce1d7..e74100ec93 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -40,7 +40,7 @@ module ApplicationTests with_rails_env "test" do rails "generate", "model", "product", "name:string" rails "db:create", "db:migrate" - output = Dir.chdir(app_path) { rails("db:test:prepare", "test") } + output = rails("db:test:prepare", "test") refute_match(/ActiveRecord::ProtectedEnvironmentError/, output) end @@ -101,6 +101,7 @@ module ApplicationTests add_to_config <<-RUBY rake_tasks do task do_nothing: :environment do + puts 'There is nothing' end end RUBY @@ -113,10 +114,8 @@ module ApplicationTests raise 'should not be pre-required for rake even eager_load=true' RUBY - Dir.chdir(app_path) do - assert system("bin/rails do_nothing RAILS_ENV=production"), - "should not be pre-required for rake even eager_load=true" - end + output = rails("do_nothing", "RAILS_ENV=production") + assert_match "There is nothing", output end def test_code_statistics_sanity @@ -294,9 +293,8 @@ module ApplicationTests def test_scaffold_tests_pass_by_default rails "generate", "scaffold", "user", "username:string", "password:string" - output = Dir.chdir(app_path) do - `RAILS_ENV=test bin/rails db:migrate test` - end + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) @@ -313,9 +311,8 @@ module ApplicationTests RUBY rails "generate", "scaffold", "user", "username:string", "password:string" - output = Dir.chdir(app_path) do - `RAILS_ENV=test bin/rails db:migrate test` - end + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) @@ -325,9 +322,8 @@ module ApplicationTests rails "generate", "model", "Product" rails "generate", "model", "Cart" rails "generate", "scaffold", "LineItems", "product:references", "cart:belongs_to" - output = Dir.chdir(app_path) do - `RAILS_ENV=test bin/rails db:migrate test` - end + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) @@ -337,9 +333,7 @@ module ApplicationTests add_to_config "config.active_record.schema_format = :sql" rails "generate", "scaffold", "user", "username:string" rails "db:migrate" - output = with_rails_env("test") do - rails "db:test:prepare", "--trace" - end + output = rails("db:test:prepare", "--trace") assert_match(/Execute db:test:load_structure/, output) end @@ -372,14 +366,12 @@ module ApplicationTests end def test_copy_templates - Dir.chdir(app_path) do - rails "app:templates:copy" - %w(controller mailer scaffold).each do |dir| - assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir)) - end - %w(controller helper scaffold_controller assets).each do |dir| - assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir)) - end + rails "app:templates:copy" + %w(controller mailer scaffold).each do |dir| + assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir)) + end + %w(controller helper scaffold_controller assets).each do |dir| + assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir)) end end diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 6db9a3b9e8..2238f4f63a 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -42,7 +42,7 @@ module ApplicationTests pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: slave, out: slave, err: slave) assert_output("Listening", master) - Dir.chdir(app_path) { system("bin/rails restart") } + rails("restart") assert_output("Restarting", master) assert_output("Inherited", master) diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 7eb26c355c..45ab8d87ff 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -172,21 +172,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase end def parse_arguments(args) - Rails::Command::ConsoleCommand.class_eval do - alias_method :old_perform, :perform - define_method(:perform) do - extract_environment_option_from_argument - - options - end - end - - Rails::Command.invoke(:console, args) - ensure - Rails::Command::ConsoleCommand.class_eval do - undef_method :perform - alias_method :perform, :old_perform - undef_method :old_perform - end + command = Rails::Command::ConsoleCommand.new([], args) + command.send(:extract_environment_option_from_argument) + command.options end end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 556c2289e7..a6201e4f04 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -81,6 +81,18 @@ class Rails::ServerTest < ActiveSupport::TestCase assert_equal false, options[:caching] end + def test_early_hints_with_option + args = ["--early-hints"] + options = parse_arguments(args) + assert_equal true, options[:early_hints] + end + + def test_early_hints_is_nil_by_default + args = [] + options = parse_arguments(args) + assert_nil options[:early_hints] + end + def test_log_stdout with_rack_env nil do with_rails_env nil do diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb index 3f4c985a33..9ef61dc978 100644 --- a/railties/test/generators/argv_scrubber_test.rb +++ b/railties/test/generators/argv_scrubber_test.rb @@ -82,9 +82,8 @@ module Rails file.puts "--hello --world" file.flush - message = nil scrubber = Class.new(ARGVScrubber) { - define_method(:puts) { |msg| message = msg } + define_method(:puts) { |msg| } }.new ["new", "--rc=#{file.path}"] args = scrubber.prepare! assert_equal ["--hello", "--world"], args diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 64e9909859..967754c813 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -59,7 +59,7 @@ class NamedBaseTest < Rails::Generators::TestCase ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names end - def test_scaffold_plural_names + def test_namespaced_scaffold_plural_names g = generator ["admin/foo"] assert_name g, "admin/foos", :controller_name assert_name g, %w(admin), :controller_class_path @@ -69,7 +69,7 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, "admin.foos", :controller_i18n_scope end - def test_scaffold_plural_names_as_ruby + def test_namespaced_scaffold_plural_names_as_ruby g = generator ["Admin::Foo"] assert_name g, "Admin::Foos", :controller_name assert_name g, %w(admin), :controller_class_path diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index 227a739b71..43b60b9144 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -2,20 +2,7 @@ require "abstract_unit" -unless defined?(Rails) && defined?(Rails::Info) - module Rails - class Info; end - end -end - -require "active_support/core_ext/kernel/reporting" - class InfoTest < ActiveSupport::TestCase - def setup - Rails.send :remove_const, :Info - silence_warnings { load "rails/info.rb" } - end - def test_property_with_block_swallows_exceptions_and_ignores_property assert_nothing_raised do Rails::Info.module_eval do |