diff options
107 files changed, 1091 insertions, 1097 deletions
diff --git a/.travis.yml b/.travis.yml index d7544fecb6..c18a24bea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,14 @@ addons: sources: - sourceline: "ppa:mc3man/trusty-media" - sourceline: "ppa:ubuntuhandbook1/apps" + - mysql-5.7-trusty packages: - ffmpeg - mupdf - mupdf-tools - poppler-utils + - mysql-server + - mysql-client bundler_args: --without test --jobs 3 --retry 3 before_install: @@ -37,6 +40,9 @@ before_install: - "[[ $GEM != 'av:ujs' ]] || nvm install node" - "[[ $GEM != 'av:ujs' ]] || node --version" - "[[ $GEM != 'av:ujs' ]] || (cd actionview && npm install)" + - "[[ $GEM != 'ar:mysql2' ]] || [[ $MYSQL == 'mariadb' ]] || sudo mysql -e \"use mysql; update user set authentication_string='' where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;\"" + - "[[ $GEM != 'ar:mysql2' ]] || [[ $MYSQL == 'mariadb' ]] || sudo mysql_upgrade" + - "[[ $GEM != 'ar:mysql2' ]] || [[ $MYSQL == 'mariadb' ]] || sudo service mysql restart" before_script: # Set Sauce Labs username and access key. Obfuscated, purposefully not encrypted. @@ -45,7 +45,6 @@ gem "dalli" gem "listen", ">= 3.0.5", "< 3.2", require: false gem "libxml-ruby", platforms: :ruby gem "connection_pool", require: false -gem "i18n", "~> 1.0.1" # for railties app_generator_test gem "bootsnap", ">= 1.1.0", require: false @@ -88,7 +87,6 @@ group :storage do gem "azure-storage", require: false gem "image_processing", "~> 1.2" - gem "ffi", "<= 1.9.21" end group :ujs do diff --git a/Gemfile.lock b/Gemfile.lock index d727263c59..52cde3841a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,16 +96,16 @@ PATH GEM remote: https://rubygems.org/ specs: - activerecord-jdbc-adapter (51.1-java) - activerecord (~> 5.1.0) - activerecord-jdbcmysql-adapter (51.1-java) - activerecord-jdbc-adapter (= 51.1) + activerecord-jdbc-adapter (52.0-java) + activerecord (~> 5.2.0) + activerecord-jdbcmysql-adapter (52.0-java) + activerecord-jdbc-adapter (= 52.0) jdbc-mysql (~> 5.1.36) - activerecord-jdbcpostgresql-adapter (51.1-java) - activerecord-jdbc-adapter (= 51.1) + activerecord-jdbcpostgresql-adapter (52.0-java) + activerecord-jdbc-adapter (= 52.0) jdbc-postgres (>= 9.4, < 43) - activerecord-jdbcsqlite3-adapter (51.1-java) - activerecord-jdbc-adapter (= 51.1) + activerecord-jdbcsqlite3-adapter (52.0-java) + activerecord-jdbc-adapter (= 52.0) jdbc-sqlite3 (~> 3.8, < 3.30) addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) @@ -113,21 +113,21 @@ GEM archive-zip (0.11.0) io-like (~> 0.3.0) ast (2.4.0) - aws-eventstream (1.0.0) - aws-partitions (1.88.0) - aws-sdk-core (3.21.2) + aws-eventstream (1.0.1) + aws-partitions (1.102.0) + aws-sdk-core (3.25.0) aws-eventstream (~> 1.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-kms (1.5.0) + aws-sdk-kms (1.7.0) aws-sdk-core (~> 3) aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.13.0) + aws-sdk-s3 (1.17.1) aws-sdk-core (~> 3, >= 3.21.2) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) - aws-sigv4 (1.0.2) + aws-sigv4 (1.0.3) azure-core (0.1.14) faraday (~> 0.9) faraday_middleware (~> 0.10) @@ -164,21 +164,21 @@ GEM childprocess faraday selenium-webdriver - bootsnap (1.3.0) + bootsnap (1.3.1) msgpack (~> 1.0) - bootsnap (1.3.0-java) + bootsnap (1.3.1-java) msgpack (~> 1.0) builder (3.2.3) bunny (2.9.2) amq-protocol (~> 2.3.0) byebug (10.0.2) - capybara (3.1.1) + capybara (3.7.1) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - xpath (~> 3.0) + xpath (~> 3.1) childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) chromedriver-helper (1.2.0) @@ -217,7 +217,7 @@ GEM em-socksify (0.3.2) eventmachine (>= 1.0.0.beta.4) erubi (1.7.1) - et-orbi (1.1.2) + et-orbi (1.1.6) tzinfo event_emitter (0.2.6) eventmachine (1.2.7) @@ -237,44 +237,44 @@ GEM faye-websocket (0.10.7) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.9.21) - ffi (1.9.21-java) - ffi (1.9.21-x64-mingw32) - ffi (1.9.21-x86-mingw32) - fugit (1.1.3) - et-orbi (~> 1.1, >= 1.1.1) + ffi (1.9.25) + ffi (1.9.25-java) + ffi (1.9.25-x64-mingw32) + ffi (1.9.25-x86-mingw32) + fugit (1.1.6) + et-orbi (~> 1.1, >= 1.1.6) raabro (~> 1.1) globalid (0.4.1) activesupport (>= 4.2.0) - google-api-client (0.19.8) + google-api-client (0.23.8) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.5, < 0.7.0) httpclient (>= 2.8.1, < 3.0) mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-cloud-core (1.2.0) + signet (~> 0.9) + google-cloud-core (1.2.3) google-cloud-env (~> 1.0) - google-cloud-env (1.0.1) + google-cloud-env (1.0.2) faraday (~> 0.11) - google-cloud-storage (1.12.0) + google-cloud-storage (1.13.1) digest-crc (~> 0.4) - google-api-client (~> 0.19.0) + google-api-client (~> 0.23) google-cloud-core (~> 1.2) googleauth (~> 0.6.2) - googleauth (0.6.2) + googleauth (0.6.6) faraday (~> 0.12) jwt (>= 1.4, < 3.0) - logging (~> 2.0) memoist (~> 0.12) multi_json (~> 1.11) - os (~> 0.9) + os (>= 0.9, < 2.0) signet (~> 0.7) hiredis (0.6.1) hiredis (0.6.1-java) http_parser.rb (0.6.0) httpclient (2.8.3) - i18n (1.0.1) + i18n (1.1.0) concurrent-ruby (~> 1.0) image_processing (1.6.0) mini_magick (~> 4.0) @@ -297,10 +297,6 @@ GEM rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - little-plugger (1.1.4) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -310,12 +306,12 @@ GEM mimemagic (~> 0.3.2) memoist (0.16.0) method_source (0.9.0) - mime-types (3.1) + mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) + mime-types-data (3.2018.0812) mimemagic (0.3.2) mini_magick (4.8.0) - mini_mime (1.0.0) + mini_mime (1.0.1) mini_portile2 (2.3.0) minitest (5.11.3) minitest-bisect (1.4.0) @@ -331,20 +327,20 @@ GEM multi_json (1.13.1) multipart-post (2.0.0) mustache (1.0.5) - mustermann (1.0.2) - mysql2 (0.5.1) - mysql2 (0.5.1-x64-mingw32) - mysql2 (0.5.1-x86-mingw32) + mustermann (1.0.3) + mysql2 (0.5.2) + mysql2 (0.5.2-x64-mingw32) + mysql2 (0.5.2-x86-mingw32) nio4r (2.3.1) nio4r (2.3.1-java) - nokogiri (1.8.2) + nokogiri (1.8.4) mini_portile2 (~> 2.3.0) - nokogiri (1.8.2-java) - nokogiri (1.8.2-x64-mingw32) + nokogiri (1.8.4-java) + nokogiri (1.8.4-x64-mingw32) mini_portile2 (~> 2.3.0) - nokogiri (1.8.2-x86-mingw32) + nokogiri (1.8.4-x86-mingw32) mini_portile2 (~> 2.3.0) - os (0.9.6) + os (1.0.0) parallel (1.12.1) parser (2.5.1.2) ast (~> 2.4.0) @@ -354,9 +350,9 @@ GEM pg (1.0.0-x86-mingw32) powerpack (0.1.2) psych (3.0.2) - public_suffix (3.0.2) - puma (3.11.4) - puma (3.11.4-java) + public_suffix (3.0.3) + puma (3.12.0) + puma (3.12.0-java) que (0.14.3) qunit-selenium (0.0.4) selenium-webdriver @@ -364,11 +360,11 @@ GEM raabro (1.1.6) racc (1.4.14) rack (2.0.5) - rack-cache (1.7.2) + rack-cache (1.8.0) rack (>= 0.4) - rack-protection (2.0.1) + rack-protection (2.0.3) rack - rack-test (1.0.0) + rack-test (1.1.0) rack (>= 1.0, < 3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) @@ -380,7 +376,7 @@ GEM rb-fsevent (0.10.3) rdoc (6.0.4) redcarpet (3.2.3) - redis (4.0.1) + redis (4.0.2) redis-namespace (1.6.0) redis (>= 3.0.4) representable (3.0.4) @@ -398,7 +394,7 @@ GEM redis (>= 3.3, < 5) resque (~> 1.26) rufus-scheduler (~> 3.2) - retriable (3.1.1) + retriable (3.1.2) rubocop (0.58.2) jaro_winkler (~> 1.5.1) parallel (~> 1.10) @@ -407,14 +403,14 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-progressbar (1.9.0) + ruby-progressbar (1.10.0) ruby-vips (2.0.13) ffi (~> 1.9) ruby_dep (1.5.0) - rubyzip (1.2.1) - rufus-scheduler (3.5.0) - fugit (~> 1.1, >= 1.1.1) - sass (3.5.6) + rubyzip (1.2.2) + rufus-scheduler (3.5.2) + fugit (~> 1.1, >= 1.1.5) + sass (3.5.7) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -427,27 +423,26 @@ GEM tilt (>= 1.1, < 3) sdoc (1.0.0) rdoc (>= 5.0) - selenium-webdriver (3.12.0) + selenium-webdriver (3.14.0) childprocess (~> 0.5) rubyzip (~> 1.2) - sequel (5.8.0) - serverengine (2.0.6) + sequel (5.12.0) + serverengine (2.0.7) sigdump (~> 0.2.2) - sidekiq (5.1.3) - concurrent-ruby (~> 1.0) - connection_pool (~> 2.2, >= 2.2.0) + sidekiq (5.2.1) + connection_pool (~> 2.2, >= 2.2.2) rack-protection (>= 1.5.0) redis (>= 3.3.5, < 5) sigdump (0.2.4) - signet (0.8.1) + signet (0.9.1) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sinatra (2.0.1) + sinatra (2.0.3) mustermann (~> 1.0) rack (~> 2.0) - rack-protection (= 2.0.1) + rack-protection (= 2.0.3) tilt (~> 2.0) sneakers (2.7.0) bunny (~> 2.9.2) @@ -465,9 +460,9 @@ GEM sqlite3 (1.3.13) sqlite3 (1.3.13-x64-mingw32) sqlite3 (1.3.13-x86-mingw32) - stackprof (0.2.11) - sucker_punch (2.0.4) - concurrent-ruby (~> 1.0.0) + stackprof (0.2.12) + sucker_punch (2.1.1) + concurrent-ruby (~> 1.0) thin (1.7.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) @@ -475,21 +470,21 @@ GEM thread_safe (0.3.6) thread_safe (0.3.6-java) tilt (2.0.8) - turbolinks (5.1.1) - turbolinks-source (~> 5.1) - turbolinks-source (5.1.0) + turbolinks (5.2.0) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) tzinfo (1.2.5) thread_safe (~> 0.1) tzinfo-data (1.2018.5) tzinfo (>= 1.0.0) uber (0.1.0) - uglifier (4.1.10) + uglifier (4.1.18) execjs (>= 0.3.0, < 3) unicode-display_width (1.4.0) useragent (0.16.10) vegas (0.1.11) rack (>= 1.0.0) - w3c_validators (1.3.3) + w3c_validators (1.3.4) json (>= 1.8) nokogiri (~> 1.6) wdm (0.1.1) @@ -528,10 +523,8 @@ DEPENDENCIES dalli delayed_job delayed_job_active_record - ffi (<= 1.9.21) google-cloud-storage (~> 1.11) hiredis - i18n (~> 1.0.1) image_processing (~> 1.2) json (>= 2.0.0) kindlerb (~> 1.2.0) diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 959943016f..17c7e9a3b7 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,21 @@ +* Add `id` option to redis adapter so now you can distinguish + ActionCable's redis connections among others. Also, you can set + custom id in options. + + Before: + ``` + $ redis-cli client list + id=669 addr=127.0.0.1:46442 fd=8 name= age=18 ... + ``` + + After: + ``` + $ redis-cli client list + id=673 addr=127.0.0.1:46516 fd=8 name=ActionCable-PID-19413 age=2 ... + ``` + + *Ilia Kasianenko* + * Rails 6 requires Ruby 2.4.1 or newer. *Jeremy Daer* diff --git a/actioncable/README.md b/actioncable/README.md index a05ef1dd20..d6893dbab1 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -425,7 +425,7 @@ The above will start a cable server on port 28080. ### In app -If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`: +If you are using a server that supports the [Rack socket hijacking API](https://www.rubydoc.info/github/rack/rack/file/SPEC#label-Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`: ```ruby # config/application.rb @@ -459,7 +459,7 @@ support, which means you can use all your regular Rails models with no problems as long as you haven't committed any thread-safety sins. The Action Cable server does _not_ need to be a multi-threaded application server. -This is because Action Cable uses the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking) +This is because Action Cable uses the [Rack socket hijacking API](https://www.rubydoc.info/github/rack/rack/file/SPEC#label-Hijacking) to take over control of connections from the application server. Action Cable then manages connections internally, in a multithreaded manner, regardless of whether the application server is multi-threaded or not. So Action Cable works diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index c28951608f..ad8fa52760 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -13,7 +13,8 @@ module ActionCable # Overwrite this factory method for Redis connections if you want to use a different Redis library than the redis gem. # This is needed, for example, when using Makara proxies for distributed Redis. cattr_accessor :redis_connector, default: ->(config) do - ::Redis.new(config.slice(:url, :host, :port, :db, :password)) + config[:id] ||= "ActionCable-PID-#{$$}" + ::Redis.new(config.slice(:url, :host, :port, :db, :password, :id)) end def initialize(*) diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 3dc995331a..ac2d8ef724 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -30,14 +30,22 @@ class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest end class RedisAdapterTest::Connector < ActionCable::TestCase - test "slices url, host, port, db, and password from config" do - config = { url: 1, host: 2, port: 3, db: 4, password: 5 } + test "slices url, host, port, db, password and id from config" do + config = { url: 1, host: 2, port: 3, db: 4, password: 5, id: "Some custom ID" } assert_called_with ::Redis, :new, [ config ] do connect config.merge(other: "unrelated", stuff: "here") end end + test "adds default id if it is not specified" do + config = { url: 1, host: 2, port: 3, db: 4, password: 5, id: "ActionCable-PID-#{$$}" } + + assert_called_with ::Redis, :new, [ config ] do + connect config.except(:id) + end + end + def connect(config) ActionCable::SubscriptionAdapter::Redis.redis_connector.call(config) end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 1468a89e96..5158f23a27 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,11 @@ +* Add `perform_deliveries` to a payload of `deliver.action_mailer` notification. + + *Yoshiyuki Kinjo* + +* Change delivery logging message when `perform_deliveries` is false. + + *Yoshiyuki Kinjo* + * Allow call `assert_enqueued_email_with` with no block. Example: diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 7f22af83b0..55f701b18e 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -588,15 +588,16 @@ module ActionMailer private def set_payload_for_mail(payload, mail) - payload[:mailer] = name - payload[:message_id] = mail.message_id - payload[:subject] = mail.subject - payload[:to] = mail.to - payload[:from] = mail.from - payload[:bcc] = mail.bcc if mail.bcc.present? - payload[:cc] = mail.cc if mail.cc.present? - payload[:date] = mail.date - payload[:mail] = mail.encoded + payload[:mailer] = name + payload[:message_id] = mail.message_id + payload[:subject] = mail.subject + payload[:to] = mail.to + payload[:from] = mail.from + payload[:bcc] = mail.bcc if mail.bcc.present? + payload[:cc] = mail.cc if mail.cc.present? + payload[:date] = mail.date + payload[:mail] = mail.encoded + payload[:perform_deliveries] = mail.perform_deliveries end def method_missing(method_name, *args) diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index 87cfbfff28..25c99342c2 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -9,8 +9,13 @@ module ActionMailer # An email was delivered. def deliver(event) info do + perform_deliveries = event.payload[:perform_deliveries] recipients = Array(event.payload[:to]).join(", ") - "Sent mail to #{recipients} (#{event.duration.round(1)}ms)" + if perform_deliveries + "Sent mail to #{recipients} (#{event.duration.round(1)}ms)" + else + "Skipped sending mail to #{recipients} as `perform_deliveries` is false" + end end debug { event.payload[:mail] } diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb index 2e89758dfb..7686fd10c9 100644 --- a/actionmailer/test/log_subscriber_test.rb +++ b/actionmailer/test/log_subscriber_test.rb @@ -37,6 +37,20 @@ class AMLogSubscriberTest < ActionMailer::TestCase BaseMailer.deliveries.clear end + def test_deliver_message_when_perform_deliveries_is_false + BaseMailer.welcome_without_deliveries.deliver_now + wait + + assert_equal(1, @logger.logged(:info).size) + assert_match("Skipped sending mail to system@test.lindsaar.net as `perform_deliveries` is false", @logger.logged(:info).first) + + assert_equal(2, @logger.logged(:debug).size) + assert_match(/BaseMailer#welcome_without_deliveries: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first) + assert_match("Welcome", @logger.logged(:debug).second) + ensure + BaseMailer.deliveries.clear + end + def test_receive_is_notified fixture = File.read(File.expand_path("fixtures/raw_email", __dir__)) TestMailer.receive(fixture) diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index bfaecdb658..a3101207dc 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -21,6 +21,11 @@ class BaseMailer < ActionMailer::Base mail(template_name: "welcome", template_path: path) end + def welcome_without_deliveries + mail(template_name: "welcome") + mail.perform_deliveries = false + end + def html_only(hash = {}) mail(hash) end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index c544ec96cf..7781980cab 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,4 +1,4 @@ -* Expose ActionController::Parameters#each_key which allows iterating over +* Expose `ActionController::Parameters#each_key` which allows iterating over keys without allocating an array. *Richard Schneeman* diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 4be4557e2c..d6911ee2b5 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -230,6 +230,12 @@ module ActionController # This method will overwrite an existing Cache-Control header. # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. # + # HTTP Cache-Control Extensions for Stale Content. See https://tools.ietf.org/html/rfc5861 + # It helps to cache an asset and serve it while is being revalidated and/or returning with an error. + # + # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds + # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds, stale_if_error: 5.minutes + # # The method will also ensure an HTTP Date header for client compatibility. def expires_in(seconds, options = {}) response.cache_control.merge!( diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 46b3b1fa25..82add522df 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -76,7 +76,9 @@ *Simon Coffey* * Extract the `confirm` call in its own, overridable method in `rails_ujs`. - Example : + + Example: + Rails.confirm = function(message, element) { return (my_bootstrap_modal_confirm(message)); } @@ -84,7 +86,9 @@ *Mathieu Mahé* * Enable select tag helper to mark `prompt` option as `selected` and/or `disabled` for `required` - field. Example: + field. + + Example: select :post, :category, @@ -92,7 +96,9 @@ { selected: "", disabled: "", prompt: "Choose one" }, { required: true } - Placeholder option would be selected and disabled. The HTML produced: + Placeholder option would be selected and disabled. + + The HTML produced: <select required="required" name="post[category]" id="post_category"> <option disabled="disabled" selected="selected" value="">Choose one</option> diff --git a/actionview/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb index 2a378fdc3c..18eaee5d79 100644 --- a/actionview/lib/action_view/buffers.rb +++ b/actionview/lib/action_view/buffers.rb @@ -3,6 +3,21 @@ require "active_support/core_ext/string/output_safety" module ActionView + # Used as a buffer for views + # + # The main difference between this and ActiveSupport::SafeBuffer + # is for the methods `<<` and `safe_expr_append=` the inputs are + # checked for nil before they are assigned and `to_s` is called on + # the input. For example: + # + # obuf = ActionView::OutputBuffer.new "hello" + # obuf << 5 + # puts obuf # => "hello5" + # + # sbuf = ActiveSupport::SafeBuffer.new "hello" + # sbuf << 5 + # puts sbuf # => "hello\u0005" + # class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: def initialize(*) super diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 39cdecb9e4..6d2e471a44 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -18,9 +18,12 @@ module ActionView # * <tt>name</tt> - Template name # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> # * <tt>dependencies</tt> - An array of dependent views - def digest(name:, finder:, dependencies: []) - dependencies ||= [] - cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + def digest(name:, finder:, dependencies: nil) + if dependencies.nil? || dependencies.empty? + cache_key = "#{name}.#{finder.rendered_format}" + else + cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + end # this is a correctly done double-checked locking idiom # (Concurrent::Map's lookups have volatile semantics) @@ -30,7 +33,7 @@ module ActionView root = tree(name, finder, partial) dependencies.each do |injected_dep| root.children << Injected.new(injected_dep, nil, nil) - end + end if dependencies finder.digest_cache[cache_key] = root.digest(finder) end end diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index 92f7ddb70d..c87c212cc7 100644 --- a/actionview/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb @@ -36,6 +36,10 @@ module ActionView # </body> # </html> # + # The return of capture is the string generated by the block. For Example: + # + # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500" + # def capture(*args) value = nil buffer = with_output_buffer { value = yield(*args) } diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb index bd0cd10eaf..56e17e67a8 100644 --- a/actionview/test/activerecord/relation_cache_test.rb +++ b/actionview/test/activerecord/relation_cache_test.rb @@ -19,5 +19,5 @@ class RelationCacheTest < ActionView::TestCase assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}") end - def view_cache_dependencies; end + def view_cache_dependencies; []; end end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 8782eb4430..6ff7ddba0f 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -10,7 +10,7 @@ module RenderTestCases def setup_view(paths) @assigns = { secret: "in the sauce" } @view = Class.new(ActionView::Base) do - def view_cache_dependencies; end + def view_cache_dependencies; []; end def combined_fragment_cache_key(key) [ :views, key ] diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb index 9a14c33d80..d8384c81b6 100644 --- a/activejob/lib/active_job/exceptions.rb +++ b/activejob/lib/active_job/exceptions.rb @@ -9,6 +9,7 @@ module ActiveJob module ClassMethods # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts. + # The number of attempts includes the total executions of a job, not just the retried executions. # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a # holding queue for inspection. @@ -21,7 +22,8 @@ module ActiveJob # as a computing proc that the number of executions so far as an argument, or as a symbol reference of # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt> # (first wait 3s, then 18s, then 83s, etc) - # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts) + # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts), + # attempts here refers to the total number of times the job is executed, not just retried executions # * <tt>:queue</tt> - Re-enqueues the job on a different queue # * <tt>:priority</tt> - Re-enqueues the job with a different priority # diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb index 0ba93c6e0b..7dc49310ac 100644 --- a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb @@ -16,12 +16,12 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :backburner class BackburnerAdapter def enqueue(job) #:nodoc: - Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name + Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority) end def enqueue_at(job, timestamp) #:nodoc: delay = timestamp - Time.current.to_f - Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay + Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay) end class JobWrapper #:nodoc: diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb index 4041f5f8c0..2e8d2ef7c0 100644 --- a/activejob/test/cases/logging_test.rb +++ b/activejob/test/cases/logging_test.rb @@ -160,7 +160,7 @@ class LoggingTest < ActiveSupport::TestCase end def test_job_error_logging - RescueJob.perform_later "other" + perform_enqueued_jobs { RescueJob.perform_later "other" } rescue RescueJob::OtherError assert_match(/Performing RescueJob \(Job ID: .*?\) from .*? with arguments:.*other/, @logger.messages) assert_match(/Error performing RescueJob \(Job ID: .*?\) from .*? in .*ms: RescueJob::OtherError \(Bad hair\):\n.*\brescue_job\.rb:\d+:in `perform'/, @logger.messages) diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index 32afb5ca62..96253773c7 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -137,4 +137,16 @@ class QueuingTest < ActiveSupport::TestCase assert job_executed "#{@id}.2" assert job_executed_at("#{@id}.2") < job_executed_at("#{@id}.1") end + + test "should run job with higher priority first in Backburner" do + skip unless adapter_is?(:backburner) + + jobs_manager.tube.pause(3) + TestJob.set(priority: 20).perform_later "#{@id}.1" + TestJob.set(priority: 10).perform_later "#{@id}.2" + wait_for_jobs_to_finish_for(10.seconds) + assert job_executed "#{@id}.1" + assert job_executed "#{@id}.2" + assert job_executed_at("#{@id}.2") < job_executed_at("#{@id}.1") + end end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 0b2fa37787..6048911217 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -6,17 +6,17 @@ * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps. - Before: - ``` - contact = Contact.new(created_at: Time.utc(2006, 8, 1)) - contact.as_json["created_at"] # => 2006-08-01 00:00:00 UTC - ``` - - After: - ``` - contact = Contact.new(created_at: Time.utc(2006, 8, 1)) - contact.as_json["created_at"] # => "2006-08-01T00:00:00.000Z" - ``` + Before: + ``` + contact = Contact.new(created_at: Time.utc(2006, 8, 1)) + contact.as_json["created_at"] # => 2006-08-01 00:00:00 UTC + ``` + + After: + ``` + contact = Contact.new(created_at: Time.utc(2006, 8, 1)) + contact.as_json["created_at"] # => "2006-08-01T00:00:00.000Z" + ``` *Bogdan Gusiev* @@ -25,6 +25,7 @@ change. There is a new method `#authenticate_XXX` where XXX is the configured attribute name, making the existing `#authenticate` now an alias for this when the attribute is the default 'password'. + Example: class User < ActiveRecord::Base @@ -36,7 +37,7 @@ user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uX..." user.authenticate_recovery_password('42password') # => user - *Unathi Chonco* + *Unathi Chonco* * Add `config.active_model.i18n_full_message` in order to control whether the `full_message` error format can be overridden at the attribute or model diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index edc30ee64d..af94d52d45 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -379,9 +379,11 @@ module ActiveModel # * <tt>errors.format</tt> def full_message(attribute, message) return message if attribute == :base + attribute = attribute.to_s if self.class.i18n_full_message && @base.class.respond_to?(:i18n_scope) - parts = attribute.to_s.split(".") + attribute = attribute.remove(/\[\d\]/) + parts = attribute.split(".") attribute_name = parts.pop namespace = parts.join("/") unless parts.empty? attributes_scope = "#{@base.class.i18n_scope}.errors.models" @@ -410,8 +412,9 @@ module ActiveModel defaults << :"errors.format" defaults << "%{attribute} %{message}" - attr_name = attribute.to_s.tr(".", "_").humanize + attr_name = attribute.tr(".", "_").humanize attr_name = @base.class.human_attribute_name(attribute, default: attr_name) + I18n.t(defaults.shift, default: defaults, attribute: attr_name, diff --git a/activemodel/lib/active_model/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb index 16e14f9e5f..473cdb0c67 100644 --- a/activemodel/lib/active_model/type/helpers/numeric.rb +++ b/activemodel/lib/active_model/type/helpers/numeric.rb @@ -29,7 +29,7 @@ module ActiveModel # 'wibble'.to_i will give zero, we want to make sure # that we aren't marking int zero to string zero as # changed. - value.to_s !~ /\A-?\d+\.?\d*\z/ + !/\A[-+]?\d+/.match?(value.to_s) end end end diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb index c0cf6ce590..be60c4f7fa 100644 --- a/activemodel/test/cases/type/decimal_test.rb +++ b/activemodel/test/cases/type/decimal_test.rb @@ -57,9 +57,12 @@ module ActiveModel def test_changed? type = Decimal.new - assert type.changed?(5.0, 5.0, "5.0wibble") + assert type.changed?(0.0, 0, "wibble") + assert type.changed?(5.0, 0, "wibble") + assert_not type.changed?(5.0, 5.0, "5.0wibble") assert_not type.changed?(5.0, 5.0, "5.0") assert_not type.changed?(-5.0, -5.0, "-5.0") + assert_not type.changed?(5.0, 5.0, "0.5e+1") end def test_scale_is_applied_before_precision_to_prevent_rounding_errors diff --git a/activemodel/test/cases/type/float_test.rb b/activemodel/test/cases/type/float_test.rb index 28318e06f8..230a8dda32 100644 --- a/activemodel/test/cases/type/float_test.rb +++ b/activemodel/test/cases/type/float_test.rb @@ -21,9 +21,12 @@ module ActiveModel def test_changing_float type = Type::Float.new - assert type.changed?(5.0, 5.0, "5wibble") + assert type.changed?(0.0, 0, "wibble") + assert type.changed?(5.0, 0, "wibble") + assert_not type.changed?(5.0, 5.0, "5wibble") assert_not type.changed?(5.0, 5.0, "5") assert_not type.changed?(5.0, 5.0, "5.0") + assert_not type.changed?(500.0, 500.0, "0.5E+4") assert_not type.changed?(nil, nil, nil) end end diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb index 8c5d18c9b3..df12098974 100644 --- a/activemodel/test/cases/type/integer_test.rb +++ b/activemodel/test/cases/type/integer_test.rb @@ -53,9 +53,13 @@ module ActiveModel test "changed?" do type = Type::Integer.new - assert type.changed?(5, 5, "5wibble") + assert type.changed?(0, 0, "wibble") + assert type.changed?(5, 0, "wibble") + assert_not type.changed?(5, 5, "5wibble") assert_not type.changed?(5, 5, "5") assert_not type.changed?(5, 5, "5.0") + assert_not type.changed?(5, 5, "+5") + assert_not type.changed?(5, 5, "+5.0") assert_not type.changed?(-5, -5, "-5") assert_not type.changed?(-5, -5, "-5.0") assert_not type.changed?(nil, nil, nil) diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 1c62e750de..ccb565c5bd 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -35,7 +35,7 @@ class I18nValidationTest < ActiveModel::TestCase def test_errors_full_messages_translates_human_attribute_name_for_model_attributes @person.errors.add(:name, "not found") - assert_called_with(Person, :human_attribute_name, [:name, default: "Name"], returns: "Person's name") do + assert_called_with(Person, :human_attribute_name, ["name", default: "Name"], returns: "Person's name") do assert_equal ["Person's name not found"], @person.errors.full_messages end end @@ -101,6 +101,63 @@ class I18nValidationTest < ActiveModel::TestCase assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.country', "cannot be blank") end + def test_errors_full_messages_with_indexed_deeply_nested_attributes_and_attributes_format + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { 'person/contacts/addresses': { attributes: { street: { format: "%{message}" } } } } } }) + + person = Person.new + assert_equal "cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].street', "cannot be blank") + assert_equal "Contacts/addresses country cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].country', "cannot be blank") + end + + def test_errors_full_messages_with_indexed_deeply_nested_attributes_and_model_format + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { 'person/contacts/addresses': { format: "%{message}" } } } }) + + person = Person.new + assert_equal "cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].street', "cannot be blank") + assert_equal "cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].country', "cannot be blank") + end + + def test_errors_full_messages_with_indexed_deeply_nested_attributes_and_i18n_attribute_name + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + attributes: { 'person/contacts/addresses': { country: "Country" } } + }) + + person = Person.new + assert_equal "Contacts/addresses street cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].street', "cannot be blank") + assert_equal "Country cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].country', "cannot be blank") + end + + def test_errors_full_messages_with_indexed_deeply_nested_attributes_without_i18n_config + ActiveModel::Errors.i18n_full_message = false + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { 'person/contacts/addresses': { attributes: { street: { format: "%{message}" } } } } } }) + + person = Person.new + assert_equal "Contacts[0]/addresses[0] street cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].street', "cannot be blank") + assert_equal "Contacts[0]/addresses[0] country cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].country', "cannot be blank") + end + + def test_errors_full_messages_with_i18n_attribute_name_without_i18n_config + ActiveModel::Errors.i18n_full_message = false + + I18n.backend.store_translations("en", activemodel: { + attributes: { 'person/contacts[0]/addresses[0]': { country: "Country" } } + }) + + person = Person.new + assert_equal "Contacts[0]/addresses[0] street cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].street', "cannot be blank") + assert_equal "Country cannot be blank", person.errors.full_message(:'contacts[0]/addresses[0].country', "cannot be blank") + end + # ActiveModel::Validations # A set of common cases for ActiveModel::Validations message generation that diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a7c2680015..336946b756 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,6 +1,32 @@ -* ActiveRecord::Base.configurations now returns an object. +* Use MySQL utf8mb4 character set by default. - ActiveRecord::Base.configurations used to return a hash, but this + `utf8mb4` character set with 4-Byte encoding supports supplementary characters including emoji. + The previous default 3-Byte encoding character set `utf8` is not enough to support them. + + *Yasuo Honda* + +* Fix duplicated record creation when using nested attributes with `create_with`. + + *Darwin Wu* + +* Configuration item `config.filter_parameters` could also filter out sensitive value of database column when call `#inspect`. + + ``` + Rails.application.config.filter_parameters += [:credit_card_number] + Account.last.inspect # => #<Account id: 123, credit_card_number: [FILTERED] ...> + ``` + + *Zhang Kang* + +* Deprecate `column_name_length`, `table_name_length`, `columns_per_table`, + `indexes_per_table`, `columns_per_multicolumn_index`, `sql_query_length`, + and `joins_per_query` methods in `DatabaseLimits`. + + *Ryuta Kamizono* + +* `ActiveRecord::Base.configurations` now returns an object. + + `ActiveRecord::Base.configurations` used to return a hash, but this is an inflexible data model. In order to improve multiple-database handling in Rails, we've changed this to return an object. Some methods are provided to make the object behave hash-like in order to ease the @@ -9,7 +35,7 @@ a deprecation warning if used, however calling `ActiveRecord::Base.configurations` will use the new version internally and externally. - For example, the following database.yml... + For example, the following `database.yml`: ``` development: @@ -34,13 +60,14 @@ Iterating over the database configurations has also changed. Instead of calling hash methods on the `configurations` hash directly, a new method `configs_for` has - been provided that allows you to select the correct configuration. `env_name` is a required - argument, `spec_name` is optional as well as passing a block. These return an array of - database config objects for the requested environment and specification name respectively. + been provided that allows you to select the correct configuration. `env_name`, and + `spec_name` arguments are optional. For example these return an array of + database config objects for the requested environment and a single database config object + will be returned for the requested environment and specification name respectively. ``` - ActiveRecord::Base.configurations.configs_for("development") - ActiveRecord::Base.configurations.configs_for("development", "primary") + ActiveRecord::Base.configurations.configs_for(env_name: "development") + ActiveRecord::Base.configurations.configs_for(env_name: "development", spec_name: "primary") ``` *Eileen M. Uchitelle*, *Aaron Patterson* @@ -133,8 +160,8 @@ *Eddie Lebow* -* Add ActiveRecord::Base.create_or_find_by/! to deal with the SELECT/INSERT race condition in - ActiveRecord::Base.find_or_create_by/! by leaning on unique constraints in the database. +* Add `ActiveRecord::Base.create_or_find_by`/`!` to deal with the SELECT/INSERT race condition in + `ActiveRecord::Base.find_or_create_by`/`!` by leaning on unique constraints in the database. *DHH* diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 170c95b827..fae56a51bb 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -94,8 +94,8 @@ namespace :db do desc "Build the MySQL test databases" task :build do config = ARTest.config["connections"]["mysql2"] - %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) + %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) end desc "Drop the MySQL test databases" diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index dfba78614e..61581b0451 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -16,7 +16,7 @@ module ActiveRecord collection = collection.send(:apply_join_dependency) end column_type = type_for_attribute(timestamp_column) - column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column)) + column = connection.visitor.compile(collection.arel_attribute(timestamp_column)) select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" if collection.has_limit_or_offset? diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 7a9e7add24..ad148efcfe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/deprecation" + module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits @@ -12,11 +14,13 @@ module ActiveRecord def column_name_length 64 end + deprecate :column_name_length # Returns the maximum length of a table name. def table_name_length 64 end + deprecate :table_name_length # Returns the maximum allowed length for an index name. This # limit is enforced by \Rails and is less than or equal to @@ -36,16 +40,19 @@ module ActiveRecord def columns_per_table 1024 end + deprecate :columns_per_table # Returns the maximum number of indexes per table. def indexes_per_table 16 end + deprecate :indexes_per_table # Returns the maximum number of columns in a multicolumn index. def columns_per_multicolumn_index 16 end + deprecate :columns_per_multicolumn_index # Returns the maximum number of elements in an IN (x,y,z) clause. # +nil+ means no limit. @@ -57,11 +64,13 @@ module ActiveRecord def sql_query_length 1048575 end + deprecate :sql_query_length # Returns maximum number of joins in a single query. def joins_per_query 256 end + deprecate :joins_per_query end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 1d36c3c8b1..fdc9ffa688 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -20,7 +20,7 @@ module ActiveRecord raise "Passing bind parameters with an arel AST is forbidden. " \ "The values must be stored on the AST directly" end - sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value + sql, binds = visitor.compile(arel_or_sql_string.ast, collector) [sql.freeze, binds || []] else [arel_or_sql_string.dup.freeze, binds] @@ -32,11 +32,11 @@ module ActiveRecord # can be used to query the database repeatedly. def cacheable_query(klass, arel) # :nodoc: if prepared_statements - sql, binds = visitor.accept(arel.ast, collector).value + sql, binds = visitor.compile(arel.ast, collector) query = klass.query(sql) else collector = PartialQueryCollector.new - parts, binds = visitor.accept(arel.ast, collector).value + parts, binds = visitor.compile(arel.ast, collector) query = klass.partial_query(parts) end [query, binds] 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 84ea7a0c33..4a6e915b66 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -602,6 +602,7 @@ module ActiveRecord # The +type+ and +options+ parameters will be ignored if present. It can be helpful # to provide these in a migration's +change+ method so it can be reverted. # In that case, +type+ and +options+ will be used by #add_column. + # Indexes on the column are automatically removed. def remove_column(table_name, column_name, type = nil, options = {}) execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index a62651daff..42ed9ce82d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -360,6 +360,10 @@ module ActiveRecord def enable_extension(name) end + def advisory_locks_enabled? # :nodoc: + supports_advisory_locks? && @advisory_locks_enabled + end + # This is meant to be implemented by the adapters that support advisory # locks # @@ -490,11 +494,7 @@ module ActiveRecord end def column_name_for_operation(operation, node) # :nodoc: - column_name_from_arel_node(node) - end - - def column_name_from_arel_node(node) # :nodoc: - visitor.accept(node, Arel::Collectors::SQLString.new).value + visitor.compile(node) end def default_index_type?(index) # :nodoc: 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 ad045f85ef..2d287d56e8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -111,7 +111,7 @@ module ActiveRecord end def supports_advisory_locks? - @advisory_locks_enabled + true end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: @@ -241,7 +241,7 @@ module ActiveRecord end # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>. - # Charset defaults to utf8. + # Charset defaults to utf8mb4. # # Example: # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin' @@ -251,7 +251,7 @@ module ActiveRecord if options[:collation] execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}" else - execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8mb4')}" end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 30e651ee63..3ee344a249 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -298,7 +298,7 @@ module ActiveRecord end def supports_advisory_locks? - @advisory_locks_enabled + true end def supports_explain? diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 9ec6ba14fd..82cf7563a2 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -8,6 +8,8 @@ module ActiveRecord module Core extend ActiveSupport::Concern + FILTERED = "[FILTERED]" # :nodoc: + included do ## # :singleton-method: @@ -123,6 +125,10 @@ module ActiveRecord class_attribute :default_connection_handler, instance_writer: false + ## + # Specifies columns which don't want to be exposed while calling #inspect + class_attribute :filter_attributes, instance_writer: false, default: [] + def self.connection_handler ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler end @@ -487,12 +493,17 @@ module ActiveRecord # Returns the contents of the record as a nicely formatted string. def inspect + filter_attributes = self.filter_attributes.map(&:to_s).to_set # We check defined?(@attributes) not to issue warnings if the object is # allocated but not initialized. inspection = if defined?(@attributes) && @attributes self.class.attribute_names.collect do |name| if has_attribute?(name) - "#{name}: #{attribute_for_inspect(name)}" + if filter_attributes.include?(name) && !read_attribute(name).nil? + "#{name}: #{ActiveRecord::Core::FILTERED}" + else + "#{name}: #{attribute_for_inspect(name)}" + end end end.compact.join(", ") else @@ -506,6 +517,7 @@ module ActiveRecord # when pp is required. def pretty_print(pp) return super if custom_inspect_method_defined? + filter_attributes = self.filter_attributes.map(&:to_s).to_set pp.object_address_group(self) do if defined?(@attributes) && @attributes column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } @@ -516,7 +528,11 @@ module ActiveRecord pp.text column_name pp.text ":" pp.breakable - pp.pp column_value + if filter_attributes.include?(column_name) && !column_value.nil? + pp.text ActiveRecord::Core::FILTERED + else + pp.pp column_value + end end end else diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb index 64dd972a47..9aabde676a 100644 --- a/activerecord/lib/active_record/database_configurations.rb +++ b/activerecord/lib/active_record/database_configurations.rb @@ -18,8 +18,8 @@ module ActiveRecord # Collects the configs for the environment and optionally the specification # name passed in. To include replica configurations pass `include_replicas: true`. # - # If a spec name is provided a single DatabaseConfiguration object will be - # returned, otherwise an array of DatabaseConfiguration objects will be + # If a spec name is provided a single DatabaseConfig object will be + # returned, otherwise an array of DatabaseConfig objects will be # returned that corresponds with the environment and type requested. # # Options: @@ -32,7 +32,7 @@ module ActiveRecord # the returned list. Most of the time we're only iterating over the write # connection (i.e. migrations don't need to run for the write and read connection). # Defaults to +false+. - def configs_for(env_name: nil, spec_name: nil, include_replicas: false, &blk) + def configs_for(env_name: nil, spec_name: nil, include_replicas: false) configs = env_with_configs(env_name) unless include_replicas @@ -53,7 +53,7 @@ module ActiveRecord # Returns the config hash that corresponds with the environment # # If the application has multiple databases `default_hash` will - # the first config hash for the environment. + # return the first config hash for the environment. # # { database: "my_db", adapter: "mysql2" } def default_hash(env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s) @@ -64,7 +64,7 @@ module ActiveRecord # Returns a single DatabaseConfig object based on the requested environment. # - # If the application has multiple databases `select_db_config` will return + # If the application has multiple databases `find_db_config` will return # the first DatabaseConfig for the environment. def find_db_config(env) configurations.find do |db_config| @@ -73,7 +73,7 @@ module ActiveRecord end end - # Returns the DatabaseConfig object as a Hash. + # Returns the DatabaseConfigurations object as a Hash. def to_h configs = configurations.reverse.inject({}) do |memo, db_config| memo.merge(db_config.to_legacy_hash) diff --git a/activerecord/lib/active_record/database_configurations/hash_config.rb b/activerecord/lib/active_record/database_configurations/hash_config.rb index 18ed7c0466..13ffe566cf 100644 --- a/activerecord/lib/active_record/database_configurations/hash_config.rb +++ b/activerecord/lib/active_record/database_configurations/hash_config.rb @@ -12,7 +12,7 @@ module ActiveRecord # Becomes: # # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 - # @env_name="development", @spec_name="primary", @config={"db_name"}> + # @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}> # # Options are: # diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 6891c575c7..b25057acda 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -55,6 +55,10 @@ module ActiveRecord if has_attribute?(inheritance_column) subclass = subclass_from_attributes(attributes) + if subclass.nil? && scope_attributes = current_scope&.scope_for_create + subclass = subclass_from_attributes(scope_attributes) + end + if subclass.nil? && base_class? subclass = subclass_from_attributes(column_defaults) end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index d68ed3cad9..ea53324829 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1357,7 +1357,7 @@ module ActiveRecord end def use_advisory_lock? - Base.connection.supports_advisory_locks? + Base.connection.advisory_locks_enabled? end def with_advisory_lock diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 24af56cf0b..6eb2bfb452 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -70,17 +70,6 @@ module ActiveRecord instantiate_instance_of(klass, attributes, column_types, &block) end - # Given a class, an attributes hash, +instantiate_instance_of+ returns a - # new instance of the class. Accepts only keys as strings. - # - # This is private, don't call it. :) - # - # :nodoc: - def instantiate_instance_of(klass, attributes, column_types = {}, &block) - attributes = klass.attributes_builder.build_from_database(attributes, column_types) - klass.allocate.init_from_db(attributes, &block) - end - # Updates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # @@ -216,6 +205,13 @@ module ActiveRecord end private + # Given a class, an attributes hash, +instantiate_instance_of+ returns a + # new instance of the class. Accepts only keys as strings. + def instantiate_instance_of(klass, attributes, column_types = {}, &block) + attributes = klass.attributes_builder.build_from_database(attributes, column_types) + klass.allocate.init_from_db(attributes, &block) + end + # Called by +instantiate+ to decide which class to use for a new # record instance. # diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 7ece083fd4..47351588d3 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -235,5 +235,11 @@ MSG end end end + + initializer "active_record.set_filter_attributes" do + ActiveSupport.on_load(:active_record) do + self.filter_attributes += Rails.application.config.filter_parameters + end + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 29a3ceab7d..86e5d14c81 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -62,7 +62,7 @@ module ActiveRecord # user = users.new { |user| user.name = 'Oscar' } # user.name # => Oscar def new(attributes = nil, &block) - scoping { klass.new(scope_for_create(attributes), &block) } + scoping { klass.new(attributes, &block) } end alias build new @@ -87,11 +87,7 @@ module ActiveRecord # users.create(name: nil) # validation on name # # => #<User id: nil, name: nil, ...> def create(attributes = nil, &block) - if attributes.is_a?(Array) - attributes.collect { |attr| create(attr, &block) } - else - scoping { klass.create(scope_for_create(attributes), &block) } - end + scoping { klass.create(attributes, &block) } end # Similar to #create, but calls @@ -101,11 +97,7 @@ module ActiveRecord # Expects arguments in the same format as # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]. def create!(attributes = nil, &block) - if attributes.is_a?(Array) - attributes.collect { |attr| create!(attr, &block) } - else - scoping { klass.create!(scope_for_create(attributes), &block) } - end + scoping { klass.create!(attributes, &block) } end def first_or_create(attributes = nil, &block) # :nodoc: @@ -315,10 +307,7 @@ module ActiveRecord # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. def scoping - previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass - yield - ensure - klass.current_scope = previous unless @delegate_to_klass + @delegate_to_klass ? yield : klass._scoping(self) { yield } end def _exec_scope(*args, &block) # :nodoc: @@ -554,10 +543,8 @@ module ActiveRecord where_clause.to_h(relation_table_name) end - def scope_for_create(attributes = nil) - scope = where_values_hash.merge!(create_with_value.stringify_keys) - scope.merge!(attributes) if attributes - scope + def scope_for_create + where_values_hash.merge!(create_with_value.stringify_keys) end # Returns true if relation needs eager loading. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c5562c1ff0..0fabfe5518 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -398,7 +398,7 @@ module ActiveRecord def limited_ids_for(relation) values = @klass.connection.columns_for_distinct( - connection.column_name_from_arel_node(arel_attribute(primary_key)), + connection.visitor.compile(arel_attribute(primary_key)), relation.order_values ) diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 01ac56570a..9eba1254a4 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -12,14 +12,6 @@ module ActiveRecord end module ClassMethods # :nodoc: - def current_scope(skip_inherited_scope = false) - ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) - end - - def current_scope=(scope) - ScopeRegistry.set_value_for(:current_scope, self, scope) - end - # Collects attributes from scopes that should be applied when creating # an AR instance for the particular class this is called on. def scope_attributes @@ -30,6 +22,15 @@ module ActiveRecord def scope_attributes? current_scope end + + private + def current_scope(skip_inherited_scope = false) + ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) + end + + def current_scope=(scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) + end end def populate_with_current_scope_attributes # :nodoc: diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 8c612df27a..6caf9b3251 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -31,7 +31,14 @@ module ActiveRecord # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } def unscoped - block_given? ? relation.scoping { yield } : relation + block_given? ? _scoping(relation) { yield } : relation + end + + def _scoping(relation) # :nodoc: + previous, self.current_scope = current_scope(true), relation + yield + ensure + self.current_scope = previous end # Are there attributes associated with this scope? diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index a784001587..573d97b819 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -24,13 +24,13 @@ module ActiveRecord # You can define a scope that applies to all finders using # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope]. def all - current_scope = self.current_scope + scope = current_scope - if current_scope - if self == current_scope.klass - current_scope.clone + if scope + if self == scope.klass + scope.clone else - relation.merge!(current_scope) + relation.merge!(scope) end else default_scoped @@ -38,9 +38,7 @@ module ActiveRecord end def scope_for_association(scope = relation) # :nodoc: - current_scope = self.current_scope - - if current_scope && current_scope.empty_scope? + if current_scope&.empty_scope? scope else default_scoped(scope) @@ -182,15 +180,13 @@ module ActiveRecord if body.respond_to?(:to_proc) singleton_class.send(:define_method, name) do |*args| - scope = all - scope = scope._exec_scope(*args, &body) + scope = all._exec_scope(*args, &body) scope = scope.extending(extension) if extension scope end else singleton_class.send(:define_method, name) do |*args| - scope = all - scope = scope.scoping { body.call(*args) || scope } + scope = body.call(*args) || all scope = scope.extending(extension) if extension scope end diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb index 53c5563d93..91e9b2b70f 100644 --- a/activerecord/lib/arel/nodes/bind_param.rb +++ b/activerecord/lib/arel/nodes/bind_param.rb @@ -3,7 +3,7 @@ module Arel # :nodoc: all module Nodes class BindParam < Node - attr_accessor :value + attr_reader :value def initialize(value) @value = value diff --git a/activerecord/lib/arel/select_manager.rb b/activerecord/lib/arel/select_manager.rb index 22a04b00c6..733176ad9e 100644 --- a/activerecord/lib/arel/select_manager.rb +++ b/activerecord/lib/arel/select_manager.rb @@ -252,9 +252,9 @@ module Arel # :nodoc: all end private - def collapse(exprs, existing = nil) - exprs = exprs.unshift(existing.expr) if existing - exprs = exprs.compact.map { |expr| + def collapse(exprs) + exprs = exprs.compact + exprs.map! { |expr| if String === expr # FIXME: Don't do this automatically Arel.sql(expr) diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb index 5986fd5576..0682c066fb 100644 --- a/activerecord/lib/arel/visitors/to_sql.rb +++ b/activerecord/lib/arel/visitors/to_sql.rb @@ -67,8 +67,8 @@ module Arel # :nodoc: all @connection = connection end - def compile(node, &block) - accept(node, Arel::Collectors::SQLString.new, &block).value + def compile(node, collector = Arel::Collectors::SQLString.new, &block) + accept(node, collector, &block).value end private diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 67734d24d7..a93e5e2b40 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "cases/helper" +require "support/connection_helper" require "models/book" require "models/post" require "models/author" @@ -300,6 +301,34 @@ module ActiveRecord def test_supports_multi_insert_is_deprecated assert_deprecated { @connection.supports_multi_insert? } end + + def test_column_name_length_is_deprecated + assert_deprecated { @connection.column_name_length } + end + + def test_table_name_length_is_deprecated + assert_deprecated { @connection.table_name_length } + end + + def test_columns_per_table_is_deprecated + assert_deprecated { @connection.columns_per_table } + end + + def test_indexes_per_table_is_deprecated + assert_deprecated { @connection.indexes_per_table } + end + + def test_columns_per_multicolumn_index_is_deprecated + assert_deprecated { @connection.columns_per_multicolumn_index } + end + + def test_sql_query_length_is_deprecated + assert_deprecated { @connection.sql_query_length } + end + + def test_joins_per_query_is_deprecated + assert_deprecated { @connection.joins_per_query } + end end class AdapterForeignKeyTest < ActiveRecord::TestCase @@ -418,3 +447,27 @@ module ActiveRecord end end end + +if ActiveRecord::Base.connection.supports_advisory_locks? + class AdvisoryLocksEnabledTest < ActiveRecord::TestCase + include ConnectionHelper + + def test_advisory_locks_enabled? + assert ActiveRecord::Base.connection.advisory_locks_enabled? + + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection( + orig_connection.merge(advisory_locks: false) + ) + + assert_not ActiveRecord::Base.connection.advisory_locks_enabled? + + ActiveRecord::Base.establish_connection( + orig_connection.merge(advisory_locks: true) + ) + + assert ActiveRecord::Base.connection.advisory_locks_enabled? + end + end + end +end diff --git a/activerecord/test/cases/adapters/helpers/test_supports_advisory_locks.rb b/activerecord/test/cases/adapters/helpers/test_supports_advisory_locks.rb deleted file mode 100644 index 4905e17725..0000000000 --- a/activerecord/test/cases/adapters/helpers/test_supports_advisory_locks.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require "support/connection_helper" - -module TestSupportsAdvisoryLocks - include ConnectionHelper - - def test_supports_advisory_locks? - assert ActiveRecord::Base.connection.supports_advisory_locks? - - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection( - orig_connection.merge(advisory_locks: false) - ) - - assert_not ActiveRecord::Base.connection.supports_advisory_locks? - - ActiveRecord::Base.establish_connection( - orig_connection.merge(advisory_locks: true) - ) - - assert ActiveRecord::Base.connection.supports_advisory_locks? - end - end -end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 6fc9df5083..261fee13eb 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -106,7 +106,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase end def test_create_mysql_database_with_encoding - assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1") assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin") end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 0c0e2a116e..3103589186 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -104,8 +104,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_mysql_connection_collation_is_configured - assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection") - assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection") + assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection") + assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection") end def test_mysql_default_in_strict_mode diff --git a/activerecord/test/cases/adapters/mysql2/test_advisory_locks_disabled_test.rb b/activerecord/test/cases/adapters/mysql2/test_advisory_locks_disabled_test.rb deleted file mode 100644 index 4857900820..0000000000 --- a/activerecord/test/cases/adapters/mysql2/test_advisory_locks_disabled_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "cases/adapters/helpers/test_supports_advisory_locks" - -class Mysql2AdvisoryLocksDisabledTest < ActiveRecord::Mysql2TestCase - include TestSupportsAdvisoryLocks -end diff --git a/activerecord/test/cases/adapters/postgresql/advisory_locks_disabled_test.rb b/activerecord/test/cases/adapters/postgresql/advisory_locks_disabled_test.rb deleted file mode 100644 index f14e9baeb9..0000000000 --- a/activerecord/test/cases/adapters/postgresql/advisory_locks_disabled_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "cases/adapters/helpers/test_supports_advisory_locks" - -class PostgresqlAdvisoryLocksDisabledTest < ActiveRecord::PostgreSQLTestCase - include TestSupportsAdvisoryLocks -end diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb new file mode 100644 index 0000000000..f88cecfe2b --- /dev/null +++ b/activerecord/test/cases/filter_attributes_test.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/admin" +require "models/admin/user" +require "models/admin/account" +require "pp" + +class FilterAttributesTest < ActiveRecord::TestCase + fixtures :"admin/users", :"admin/accounts" + + setup do + ActiveRecord::Base.filter_attributes = [:name] + end + + teardown do + ActiveRecord::Base.filter_attributes = [] + end + + test "filter_attributes" do + Admin::User.all.each do |user| + assert_includes user.inspect, "name: [FILTERED]" + assert_equal 1, user.inspect.scan("[FILTERED]").length + end + + Admin::Account.all.each do |account| + assert_includes account.inspect, "name: [FILTERED]" + assert_equal 1, account.inspect.scan("[FILTERED]").length + end + end + + test "filter_attributes could be overwritten by models" do + Admin::Account.all.each do |account| + assert_includes account.inspect, "name: [FILTERED]" + assert_equal 1, account.inspect.scan("[FILTERED]").length + end + + Admin::Account.filter_attributes = [] + + # Above changes should not impact other models + Admin::User.all.each do |user| + assert_includes user.inspect, "name: [FILTERED]" + assert_equal 1, user.inspect.scan("[FILTERED]").length + end + + Admin::Account.all.each do |account| + assert_not_includes account.inspect, "name: [FILTERED]" + assert_equal 0, account.inspect.scan("[FILTERED]").length + end + + Admin::Account.filter_attributes = [:name] + end + + test "filter_attributes should not filter nil value" do + account = Admin::Account.new + + assert_includes account.inspect, "name: nil" + assert_not_includes account.inspect, "name: [FILTERED]" + assert_equal 0, account.inspect.scan("[FILTERED]").length + end + + test "filter_attributes on pretty_print" do + user = admin_users(:david) + actual = "".dup + PP.pp(user, StringIO.new(actual)) + + assert_includes actual, "name: [FILTERED]" + assert_equal 1, actual.scan("[FILTERED]").length + end + + test "filter_attributes on pretty_print should not filter nil value" do + user = Admin::User.new + actual = "".dup + PP.pp(user, StringIO.new(actual)) + + assert_includes actual, "name: nil" + assert_not_includes actual, "name: [FILTERED]" + assert_equal 0, actual.scan("[FILTERED]").length + end +end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index aa519fd332..bb1c1ea17d 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -217,6 +217,18 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase mean_pirate.parrot_attributes = { name: "James" } assert_equal "James", mean_pirate.parrot.name end + + def test_should_not_create_duplicates_with_create_with + Man.accepts_nested_attributes_for(:interests) + + assert_difference("Interest.count", 1) do + Man.create_with( + interests_attributes: [{ topic: "Pirate king" }] + ).find_or_create_by!( + name: "Monkey D. Luffy" + ) + end + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index d03b412efb..c14dc23cf3 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -9,6 +9,7 @@ require "models/comment" require "models/author" require "models/entrant" require "models/developer" +require "models/project" require "models/person" require "models/computer" require "models/reply" @@ -1405,6 +1406,16 @@ class RelationTest < ActiveRecord::TestCase assert_equal "cock", hens.new.name end + def test_create_with_nested_attributes + assert_difference("Project.count", 1) do + developers = Developer.where(name: "Aaron") + developers = developers.create_with( + projects_attributes: [{ name: "p1" }] + ) + developers.create! + end + end + def test_except relation = Post.where(author_id: 1).order("id ASC").limit(1) assert_equal [posts(:welcome)], relation.to_a diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index e3a34aa50d..6281712df6 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -4,6 +4,7 @@ require "cases/helper" require "models/post" require "models/comment" require "models/developer" +require "models/project" require "models/computer" require "models/vehicle" require "models/cat" @@ -366,6 +367,21 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal "Jamis", jamis.name end + def test_create_with_takes_precedence_over_where + developer = Developer.where(name: nil).create_with(name: "Aaron").new + assert_equal "Aaron", developer.name + end + + def test_create_with_nested_attributes + assert_difference("Project.count", 1) do + Developer.create_with( + projects_attributes: [{ name: "p1" }] + ).scoping do + Developer.create!(name: "Aaron") + end + end + end + # FIXME: I don't know if this is *desired* behavior, but it is *today's* # behavior. def test_create_with_empty_hash_will_not_reset diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 544adc9b39..b4f4379e5e 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -236,8 +236,8 @@ class RelationScopingTest < ActiveRecord::TestCase SpecialComment.unscoped.created end - assert_nil Comment.current_scope - assert_nil SpecialComment.current_scope + assert_nil Comment.send(:current_scope) + assert_nil SpecialComment.send(:current_scope) end def test_scoping_respects_current_class diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index a39a7371fe..d674bd562f 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -156,24 +156,20 @@ module ActiveRecord class DatabaseTasksCreateAllTest < ActiveRecord::TestCase def setup - @old_configurations = ActiveRecord::Base.configurations @configurations = { "development" => { "database" => "my-db" } } $stdout, @original_stdout = StringIO.new, $stdout $stderr, @original_stderr = StringIO.new, $stderr - - ActiveRecord::Base.configurations = @configurations end def teardown $stdout, $stderr = @original_stdout, @original_stderr - ActiveRecord::Base.configurations = @old_configurations end def test_ignores_configurations_without_databases @configurations["development"]["database"] = nil - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do ActiveRecord::Tasks::DatabaseTasks.create_all end @@ -183,7 +179,7 @@ module ActiveRecord def test_ignores_remote_databases @configurations["development"]["host"] = "my.server.tld" - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do ActiveRecord::Tasks::DatabaseTasks.create_all end @@ -193,7 +189,7 @@ module ActiveRecord def test_warning_for_remote_databases @configurations["development"]["host"] = "my.server.tld" - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do ActiveRecord::Tasks::DatabaseTasks.create_all assert_match "This task only modifies local databases. my-db is on a remote host.", @@ -204,7 +200,7 @@ module ActiveRecord def test_creates_configurations_with_local_ip @configurations["development"]["host"] = "127.0.0.1" - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do ActiveRecord::Tasks::DatabaseTasks.create_all end @@ -214,7 +210,7 @@ module ActiveRecord def test_creates_configurations_with_local_host @configurations["development"]["host"] = "localhost" - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do ActiveRecord::Tasks::DatabaseTasks.create_all end @@ -224,33 +220,39 @@ module ActiveRecord def test_creates_configurations_with_blank_hosts @configurations["development"]["host"] = nil - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do ActiveRecord::Tasks::DatabaseTasks.create_all end end end + + private + def with_stubbed_configurations_establish_connection + old_configurations = ActiveRecord::Base.configurations + ActiveRecord::Base.configurations = @configurations + + # To refrain from connecting to a newly created empty DB in + # sqlite3_mem tests + ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do + yield + end + ensure + ActiveRecord::Base.configurations = old_configurations + end end class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase def setup - @old_configurations = ActiveRecord::Base.configurations - @configurations = { "development" => { "database" => "dev-db" }, "test" => { "database" => "test-db" }, - "production" => { "url" => "abstract://prod-db-url" } + "production" => { "url" => "abstract://prod-db-host/prod-db" } } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations end def test_creates_current_environment_database - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, @@ -264,11 +266,11 @@ module ActiveRecord end def test_creates_current_environment_database_with_url - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, - ["url" => "prod-db-url"], + ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"], ) do ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new("production") @@ -278,7 +280,7 @@ module ActiveRecord end def test_creates_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, @@ -298,7 +300,7 @@ module ActiveRecord old_env = ENV["RAILS_ENV"] ENV["RAILS_ENV"] = "development" - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, @@ -325,27 +327,31 @@ module ActiveRecord end end end + + private + def with_stubbed_configurations_establish_connection + old_configurations = ActiveRecord::Base.configurations + ActiveRecord::Base.configurations = @configurations + + ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do + yield + end + ensure + ActiveRecord::Base.configurations = old_configurations + end end class DatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase def setup - @old_configurations = ActiveRecord::Base.configurations - @configurations = { "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } }, "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } }, - "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } } + "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } } } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations end def test_creates_current_environment_database - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, @@ -362,13 +368,13 @@ module ActiveRecord end def test_creates_current_environment_database_with_url - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, [ - ["url" => "prod-db-url"], - ["url" => "secondary-prod-db-url"] + ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"], + ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"] ] ) do ActiveRecord::Tasks::DatabaseTasks.create_current( @@ -379,7 +385,7 @@ module ActiveRecord end def test_creates_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, @@ -401,7 +407,7 @@ module ActiveRecord old_env = ENV["RAILS_ENV"] ENV["RAILS_ENV"] = "development" - ActiveRecord::Base.configurations do + with_stubbed_configurations_establish_connection do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :create, @@ -434,6 +440,18 @@ module ActiveRecord end end end + + private + def with_stubbed_configurations_establish_connection + old_configurations = ActiveRecord::Base.configurations + ActiveRecord::Base.configurations = @configurations + + ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do + yield + end + ensure + ActiveRecord::Base.configurations = old_configurations + end end class DatabaseTasksDropTest < ActiveRecord::TestCase @@ -452,24 +470,20 @@ module ActiveRecord class DatabaseTasksDropAllTest < ActiveRecord::TestCase def setup - @old_configurations = ActiveRecord::Base.configurations @configurations = { development: { "database" => "my-db" } } $stdout, @original_stdout = StringIO.new, $stdout $stderr, @original_stderr = StringIO.new, $stderr - - ActiveRecord::Base.configurations = @configurations end def teardown $stdout, $stderr = @original_stdout, @original_stderr - ActiveRecord::Base.configurations = @old_configurations end def test_ignores_configurations_without_databases @configurations[:development]["database"] = nil - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do ActiveRecord::Tasks::DatabaseTasks.drop_all end @@ -479,7 +493,7 @@ module ActiveRecord def test_ignores_remote_databases @configurations[:development]["host"] = "my.server.tld" - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do ActiveRecord::Tasks::DatabaseTasks.drop_all end @@ -489,7 +503,7 @@ module ActiveRecord def test_warning_for_remote_databases @configurations[:development]["host"] = "my.server.tld" - ActiveRecord::Base.configurations do + with_stubbed_configurations do ActiveRecord::Tasks::DatabaseTasks.drop_all assert_match "This task only modifies local databases. my-db is on a remote host.", @@ -500,7 +514,7 @@ module ActiveRecord def test_drops_configurations_with_local_ip @configurations[:development]["host"] = "127.0.0.1" - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do ActiveRecord::Tasks::DatabaseTasks.drop_all end @@ -510,7 +524,7 @@ module ActiveRecord def test_drops_configurations_with_local_host @configurations[:development]["host"] = "localhost" - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do ActiveRecord::Tasks::DatabaseTasks.drop_all end @@ -520,32 +534,35 @@ module ActiveRecord def test_drops_configurations_with_blank_hosts @configurations[:development]["host"] = nil - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do ActiveRecord::Tasks::DatabaseTasks.drop_all end end end + + private + def with_stubbed_configurations + old_configurations = ActiveRecord::Base.configurations + ActiveRecord::Base.configurations = @configurations + + yield + ensure + ActiveRecord::Base.configurations = old_configurations + end end class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase def setup - @old_configurations = ActiveRecord::Base.configurations @configurations = { "development" => { "database" => "dev-db" }, "test" => { "database" => "test-db" }, - "production" => { "url" => "abstract://prod-db-url" } + "production" => { "url" => "abstract://prod-db-host/prod-db" } } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations end def test_drops_current_environment_database - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop, ["database" => "test-db"]) do ActiveRecord::Tasks::DatabaseTasks.drop_current( @@ -556,9 +573,9 @@ module ActiveRecord end def test_drops_current_environment_database_with_url - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop, - ["url" => "prod-db-url"]) do + ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"]) do ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveSupport::StringInquirer.new("production") ) @@ -567,7 +584,7 @@ module ActiveRecord end def test_drops_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :drop, @@ -587,7 +604,7 @@ module ActiveRecord old_env = ENV["RAILS_ENV"] ENV["RAILS_ENV"] = "development" - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :drop, @@ -604,26 +621,29 @@ module ActiveRecord ensure ENV["RAILS_ENV"] = old_env end + + private + def with_stubbed_configurations + old_configurations = ActiveRecord::Base.configurations + ActiveRecord::Base.configurations = @configurations + + yield + ensure + ActiveRecord::Base.configurations = old_configurations + end end class DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase def setup - @old_configurations = ActiveRecord::Base.configurations @configurations = { "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } }, "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } }, - "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } } + "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } } } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations end def test_drops_current_environment_database - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :drop, @@ -640,13 +660,13 @@ module ActiveRecord end def test_drops_current_environment_database_with_url - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :drop, [ - ["url" => "prod-db-url"], - ["url" => "secondary-prod-db-url"] + ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"], + ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"] ] ) do ActiveRecord::Tasks::DatabaseTasks.drop_current( @@ -657,7 +677,7 @@ module ActiveRecord end def test_drops_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :drop, @@ -679,7 +699,7 @@ module ActiveRecord old_env = ENV["RAILS_ENV"] ENV["RAILS_ENV"] = "development" - ActiveRecord::Base.configurations do + with_stubbed_configurations do assert_called_with( ActiveRecord::Tasks::DatabaseTasks, :drop, @@ -698,6 +718,16 @@ module ActiveRecord ensure ENV["RAILS_ENV"] = old_env end + + private + def with_stubbed_configurations + old_configurations = ActiveRecord::Base.configurations + ActiveRecord::Base.configurations = @configurations + + yield + ensure + ActiveRecord::Base.configurations = old_configurations + end end if current_adapter?(:SQLite3Adapter) && !in_memory_db? @@ -860,15 +890,13 @@ module ActiveRecord ActiveRecord::Base.configurations = configurations - ActiveRecord::Base.configurations do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :purge, - ["database" => "prod-db"] - ) do - assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do - ActiveRecord::Tasks::DatabaseTasks.purge_current("production") - end + assert_called_with( + ActiveRecord::Tasks::DatabaseTasks, + :purge, + ["database" => "prod-db"] + ) do + assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do + ActiveRecord::Tasks::DatabaseTasks.purge_current("production") end end ensure @@ -881,14 +909,13 @@ module ActiveRecord old_configurations = ActiveRecord::Base.configurations configurations = { development: { "database" => "my-db" } } ActiveRecord::Base.configurations = configurations - ActiveRecord::Base.configurations do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :purge, - ["database" => "my-db"] - ) do - ActiveRecord::Tasks::DatabaseTasks.purge_all - end + + assert_called_with( + ActiveRecord::Tasks::DatabaseTasks, + :purge, + ["database" => "my-db"] + ) do + ActiveRecord::Tasks::DatabaseTasks.purge_all end ensure ActiveRecord::Base.configurations = old_configurations diff --git a/activerecord/test/cases/tasks/legacy_database_tasks_test.rb b/activerecord/test/cases/tasks/legacy_database_tasks_test.rb deleted file mode 100644 index ffa55be878..0000000000 --- a/activerecord/test/cases/tasks/legacy_database_tasks_test.rb +++ /dev/null @@ -1,591 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "active_record/tasks/database_tasks" - -module ActiveRecord - class LegacyDatabaseTasksCreateAllTest < ActiveRecord::TestCase - def setup - @old_configurations = ActiveRecord::Base.configurations.to_h - - @configurations = { "development" => { "database" => "my-db" } } - - $stdout, @original_stdout = StringIO.new, $stdout - $stderr, @original_stderr = StringIO.new, $stderr - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - $stdout, $stderr = @original_stdout, @original_stderr - ActiveRecord::Base.configurations = @old_configurations - end - - def test_ignores_configurations_without_databases - @configurations["development"]["database"] = nil - - ActiveRecord::Base.configurations.to_h do - assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do - ActiveRecord::Tasks::DatabaseTasks.create_all - end - end - end - - def test_ignores_remote_databases - @configurations["development"]["host"] = "my.server.tld" - - ActiveRecord::Base.configurations.to_h do - assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do - ActiveRecord::Tasks::DatabaseTasks.create_all - end - end - end - - def test_warning_for_remote_databases - @configurations["development"]["host"] = "my.server.tld" - - ActiveRecord::Base.configurations.to_h do - ActiveRecord::Tasks::DatabaseTasks.create_all - - assert_match "This task only modifies local databases. my-db is on a remote host.", - $stderr.string - end - end - - def test_creates_configurations_with_local_ip - @configurations["development"]["host"] = "127.0.0.1" - - ActiveRecord::Base.configurations.to_h do - assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do - ActiveRecord::Tasks::DatabaseTasks.create_all - end - end - end - - def test_creates_configurations_with_local_host - @configurations["development"]["host"] = "localhost" - - ActiveRecord::Base.configurations.to_h do - assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do - ActiveRecord::Tasks::DatabaseTasks.create_all - end - end - end - - def test_creates_configurations_with_blank_hosts - @configurations["development"]["host"] = nil - - ActiveRecord::Base.configurations.to_h do - assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do - ActiveRecord::Tasks::DatabaseTasks.create_all - end - end - end - end - - class LegacyDatabaseTasksCreateCurrentTest < ActiveRecord::TestCase - def setup - @old_configurations = ActiveRecord::Base.configurations.to_h - - @configurations = { - "development" => { "database" => "dev-db" }, - "test" => { "database" => "test-db" }, - "production" => { "url" => "abstract://prod-db-url" } - } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations - end - - def test_creates_current_environment_database - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - ["database" => "test-db"], - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("test") - ) - end - end - end - - def test_creates_current_environment_database_with_url - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - ["url" => "prod-db-url"], - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("production") - ) - end - end - end - - def test_creates_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - [ - ["database" => "dev-db"], - ["database" => "test-db"] - ], - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - end - - def test_creates_test_and_development_databases_when_rails_env_is_development - old_env = ENV["RAILS_ENV"] - ENV["RAILS_ENV"] = "development" - - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - [ - ["database" => "dev-db"], - ["database" => "test-db"] - ], - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - ensure - ENV["RAILS_ENV"] = old_env - end - - def test_establishes_connection_for_the_given_environments - ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do - assert_called_with(ActiveRecord::Base, :establish_connection, [:development]) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - end - end - - class LegacyDatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase - def setup - @old_configurations = ActiveRecord::Base.configurations.to_h - - @configurations = { - "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } }, - "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } }, - "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } } - } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations - end - - def test_creates_current_environment_database - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - [ - ["database" => "test-db"], - ["database" => "secondary-test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("test") - ) - end - end - end - - def test_creates_current_environment_database_with_url - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - [ - ["url" => "prod-db-url"], - ["url" => "secondary-prod-db-url"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("production") - ) - end - end - end - - def test_creates_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - [ - ["database" => "dev-db"], - ["database" => "secondary-dev-db"], - ["database" => "test-db"], - ["database" => "secondary-test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - end - - def test_creates_test_and_development_databases_when_rails_env_is_development - old_env = ENV["RAILS_ENV"] - ENV["RAILS_ENV"] = "development" - - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :create, - [ - ["database" => "dev-db"], - ["database" => "secondary-dev-db"], - ["database" => "test-db"], - ["database" => "secondary-test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - ensure - ENV["RAILS_ENV"] = old_env - end - - def test_establishes_connection_for_the_given_environments_config - ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do - assert_called_with( - ActiveRecord::Base, - :establish_connection, - [:development] - ) do - ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - end - end - - class LegacyDatabaseTasksDropAllTest < ActiveRecord::TestCase - def setup - @old_configurations = ActiveRecord::Base.configurations.to_h - - @configurations = { development: { "database" => "my-db" } } - - $stdout, @original_stdout = StringIO.new, $stdout - $stderr, @original_stderr = StringIO.new, $stderr - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - $stdout, $stderr = @original_stdout, @original_stderr - ActiveRecord::Base.configurations = @old_configurations - end - - def test_ignores_configurations_without_databases - @configurations[:development]["database"] = nil - - ActiveRecord::Base.configurations.to_h do - assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do - ActiveRecord::Tasks::DatabaseTasks.drop_all - end - end - end - - def test_ignores_remote_databases - @configurations[:development]["host"] = "my.server.tld" - - ActiveRecord::Base.configurations.to_h do - assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do - ActiveRecord::Tasks::DatabaseTasks.drop_all - end - end - end - - def test_warning_for_remote_databases - @configurations[:development]["host"] = "my.server.tld" - - ActiveRecord::Base.configurations.to_h do - ActiveRecord::Tasks::DatabaseTasks.drop_all - - assert_match "This task only modifies local databases. my-db is on a remote host.", - $stderr.string - end - end - - def test_drops_configurations_with_local_ip - @configurations[:development]["host"] = "127.0.0.1" - - ActiveRecord::Base.configurations.to_h do - assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do - ActiveRecord::Tasks::DatabaseTasks.drop_all - end - end - end - - def test_drops_configurations_with_local_host - @configurations[:development]["host"] = "localhost" - - ActiveRecord::Base.configurations.to_h do - assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do - ActiveRecord::Tasks::DatabaseTasks.drop_all - end - end - end - - def test_drops_configurations_with_blank_hosts - @configurations[:development]["host"] = nil - - ActiveRecord::Base.configurations.to_h do - assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do - ActiveRecord::Tasks::DatabaseTasks.drop_all - end - end - end - end - - class LegacyDatabaseTasksDropCurrentTest < ActiveRecord::TestCase - def setup - @old_configurations = ActiveRecord::Base.configurations.to_h - - @configurations = { - "development" => { "database" => "dev-db" }, - "test" => { "database" => "test-db" }, - "production" => { "url" => "abstract://prod-db-url" } - } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations - end - - def test_drops_current_environment_database - ActiveRecord::Base.configurations.to_h do - assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop, - ["database" => "test-db"]) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("test") - ) - end - end - end - - def test_drops_current_environment_database_with_url - ActiveRecord::Base.configurations.to_h do - assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop, - ["url" => "prod-db-url"]) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("production") - ) - end - end - end - - def test_drops_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :drop, - [ - ["database" => "dev-db"], - ["database" => "test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - end - - def test_drops_testand_development_databases_when_rails_env_is_development - old_env = ENV["RAILS_ENV"] - ENV["RAILS_ENV"] = "development" - - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :drop, - [ - ["database" => "dev-db"], - ["database" => "test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - ensure - ENV["RAILS_ENV"] = old_env - end - end - - class LegacyDatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase - def setup - @old_configurations = ActiveRecord::Base.configurations.to_h - - @configurations = { - "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } }, - "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } }, - "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } } - } - - ActiveRecord::Base.configurations = @configurations - end - - def teardown - ActiveRecord::Base.configurations = @old_configurations - end - - def test_drops_current_environment_database - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :drop, - [ - ["database" => "test-db"], - ["database" => "secondary-test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("test") - ) - end - end - end - - def test_drops_current_environment_database_with_url - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :drop, - [ - ["url" => "prod-db-url"], - ["url" => "secondary-prod-db-url"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("production") - ) - end - end - end - - def test_drops_test_and_development_databases_when_env_was_not_specified - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :drop, - [ - ["database" => "dev-db"], - ["database" => "secondary-dev-db"], - ["database" => "test-db"], - ["database" => "secondary-test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - end - - def test_drops_testand_development_databases_when_rails_env_is_development - old_env = ENV["RAILS_ENV"] - ENV["RAILS_ENV"] = "development" - - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :drop, - [ - ["database" => "dev-db"], - ["database" => "secondary-dev-db"], - ["database" => "test-db"], - ["database" => "secondary-test-db"] - ] - ) do - ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new("development") - ) - end - end - ensure - ENV["RAILS_ENV"] = old_env - end - end - - class LegacyDatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase - def test_purges_current_environment_database - @old_configurations = ActiveRecord::Base.configurations.to_h - - configurations = { - "development" => { "database" => "dev-db" }, - "test" => { "database" => "test-db" }, - "production" => { "database" => "prod-db" } - } - - ActiveRecord::Base.configurations = configurations - - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :purge, - ["database" => "prod-db"] - ) do - assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do - ActiveRecord::Tasks::DatabaseTasks.purge_current("production") - end - end - end - ensure - ActiveRecord::Base.configurations = @old_configurations - end - end - - class LegacyDatabaseTasksPurgeAllTest < ActiveRecord::TestCase - def test_purge_all_local_configurations - @old_configurations = ActiveRecord::Base.configurations.to_h - - configurations = { development: { "database" => "my-db" } } - ActiveRecord::Base.configurations = configurations - - ActiveRecord::Base.configurations.to_h do - assert_called_with( - ActiveRecord::Tasks::DatabaseTasks, - :purge, - ["database" => "my-db"] - ) do - ActiveRecord::Tasks::DatabaseTasks.purge_all - end - end - ensure - ActiveRecord::Base.configurations = @old_configurations - end - end -end diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index 4bcb2aeea6..be337ddcd8 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -54,11 +54,11 @@ connections: mysql2: arunit: username: rails - encoding: utf8 - collation: utf8_unicode_ci + encoding: utf8mb4 + collation: utf8mb4_unicode_ci arunit2: username: rails - encoding: utf8 + encoding: utf8mb4 oracle: arunit: diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 0f2f6ddd68..c384297658 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -36,7 +36,7 @@ ActiveRecord::Schema.define do t.index :var_binary end - create_table :key_tests, force: true, options: "ENGINE=MyISAM" do |t| + create_table :key_tests, force: true do |t| t.string :awesome t.string :pizza t.string :snacks diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 4ae02edd6a..d1f079b3d8 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,25 @@ +* Define `unfreeze_time` as an alias of `travel_back` in `ActiveSupport::Testing::TimeHelpers`. + + The alias is provided for symmetry with `freeze_time`. + + *Ryan Davidson* + +* Add support for tracing constant autoloads. Just throw + + ActiveSupport::Dependencies.logger = Rails.logger + ActiveSupport::Dependencies.verbose = true + + in an initializer. + + *Xavier Noria* + +* Maintain `html_safe?` on html_safe strings when sliced. + + string = "<div>test</div>".html_safe + string[-1..1].html_safe? # => true + + *Elom Gomez*, *Yumin Wong* + * Add `Array#extract!`. The method removes and returns the elements for which the block returns a true value. @@ -50,7 +72,7 @@ *Aaron "t.lo" Patterson* -* Add cpu_time, idle_time, and allocations to Event +* Add cpu_time, idle_time, and allocations to Event. *Eileen M. Uchitelle*, *Aaron Patterson* @@ -64,7 +86,7 @@ *Jason Lee* -* Allow Range#=== and Range#cover? on Range +* Allow `Range#===` and `Range#cover?` on Range. `Range#cover?` can now accept a range argument like `Range#include?` and `Range#===`. `Range#===` works correctly on Ruby 2.6. `Range#include?` is moved diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index a0f44aac0f..04c54c30d0 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -127,15 +127,19 @@ module ActiveSupport hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) - fname_paths = [] # Make sure file name doesn't exceed file system limits. - begin - fname_paths << fname[0, FILENAME_MAX_SIZE] - fname = fname[FILENAME_MAX_SIZE..-1] - end until fname.blank? + if fname.length < FILENAME_MAX_SIZE + fname_paths = fname + else + fname_paths = [] + begin + fname_paths << fname[0, FILENAME_MAX_SIZE] + fname = fname[FILENAME_MAX_SIZE..-1] + end until fname.blank? + end - File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) + File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths) end # Translate a file path into a key. diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index f3bdc2977e..d837bb10aa 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -149,9 +149,7 @@ module ActiveSupport #:nodoc: end def [](*args) - if args.size < 2 - super - elsif html_safe? + if html_safe? new_safe_buffer = super if new_safe_buffer diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 9dc2c46880..238a9f0ee6 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -79,6 +79,12 @@ module ActiveSupport #:nodoc: # to allow arbitrary constants to be marked for unloading. mattr_accessor :explicitly_unloadable_constants, default: [] + # The logger used when tracing autoloads. + mattr_accessor :logger + + # If true, trace autoloads with +logger.debug+. + mattr_accessor :verbose, default: false + # The WatchStack keeps a stack of the modules being watched as files are # loaded. If a file in the process of being loaded (parent.rb) triggers the # load of another file (child.rb) the stack will ensure that child.rb @@ -416,7 +422,7 @@ module ActiveSupport #:nodoc: # Search for a file in autoload_paths matching the provided suffix. def search_for_file(path_suffix) - path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze) + path_suffix += ".rb" unless path_suffix.ends_with?(".rb") autoload_paths.each do |root| path = File.join(root, path_suffix) @@ -450,6 +456,7 @@ module ActiveSupport #:nodoc: return nil unless base_path = autoloadable_module?(path_suffix) mod = Module.new into.const_set const_name, mod + log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})") autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) autoloaded_constants.uniq! mod @@ -491,7 +498,7 @@ module ActiveSupport #:nodoc: raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end - qualified_name = qualified_name_for from_mod, const_name + qualified_name = qualified_name_for(from_mod, const_name) path_suffix = qualified_name.underscore file_path = search_for_file(path_suffix) @@ -504,8 +511,13 @@ module ActiveSupport #:nodoc: raise "Circular dependency detected while autoloading constant #{qualified_name}" else require_or_load(expanded, qualified_name) - raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) - return from_mod.const_get(const_name) + + if from_mod.const_defined?(const_name, false) + log("constant #{qualified_name} autoloaded from #{expanded}.rb") + return from_mod.const_get(const_name) + else + raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" + end end elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) return mod @@ -554,6 +566,7 @@ module ActiveSupport #:nodoc: # as the environment will be in an inconsistent state, e.g. other constants # may have already been unloaded and not accessible. def remove_unloadable_constants! + log("removing unloadable constants") autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear Reference.clear! @@ -743,6 +756,10 @@ module ActiveSupport #:nodoc: # The constant is no longer reachable, just skip it. end end + + def log(message) + logger.debug("autoloading: #{message}") if logger && verbose + end end end diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 93bde57f6a..584930e413 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -87,9 +87,21 @@ module I18n when Hash, Array Array.wrap(fallbacks) else # TrueClass - [] + [I18n.default_locale] end + if args.empty? || args.first.is_a?(Hash) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using I18n fallbacks with an empty `defaults` sets the defaults to + include the `default_locale`. This behavior will change in Rails 6.1. + If you desire the default locale to be included in the defaults, please + explicitly configure it with `config.i18n.fallbacks.defaults = + [I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale, + {...}]` + MSG + args.unshift I18n.default_locale + end + I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) end diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb index 1caac1feb3..beeb470659 100644 --- a/activesupport/lib/active_support/testing/parallelization.rb +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -65,22 +65,24 @@ module ActiveSupport def start @pool = @queue_size.times.map do |worker| fork do - DRb.stop_service + begin + DRb.stop_service - after_fork(worker) + after_fork(worker) - queue = DRbObject.new_with_uri(@url) + queue = DRbObject.new_with_uri(@url) - while job = queue.pop - klass = job[0] - method = job[1] - reporter = job[2] - result = Minitest.run_one_method(klass, method) + while job = queue.pop + klass = job[0] + method = job[1] + reporter = job[2] + result = Minitest.run_one_method(klass, method) - queue.record(reporter, result) + queue.record(reporter, result) + end + ensure + run_cleanup(worker) end - - run_cleanup(worker) end end end diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 801ea2909b..f160e66971 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -158,7 +158,7 @@ module ActiveSupport end # Returns the current time back to its original state, by removing the stubs added by - # +travel+ and +travel_to+. + # +travel+, +travel_to+, and +freeze_time+. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) @@ -168,6 +168,7 @@ module ActiveSupport def travel_back simple_stubs.unstub_all! end + alias_method :unfreeze_time, :travel_back # Calls +travel_to+ with +Time.now+. # diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 84cb64a7c2..a486ef5eac 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -1130,3 +1130,52 @@ class DependenciesTest < ActiveSupport::TestCase ActiveSupport::Dependencies.hook! end end + +class DependenciesLogging < ActiveSupport::TestCase + MESSAGE = "message" + + def with_settings(logger, verbose) + original_logger = ActiveSupport::Dependencies.logger + original_verbose = ActiveSupport::Dependencies.verbose + + ActiveSupport::Dependencies.logger = logger + ActiveSupport::Dependencies.verbose = verbose + + yield + ensure + ActiveSupport::Dependencies.logger = original_logger + ActiveSupport::Dependencies.verbose = original_verbose + end + + def fake_logger + Class.new do + def self.debug(message) + message + end + end + end + + test "does not log if the logger is nil and verbose is false" do + with_settings(nil, false) do + assert_nil ActiveSupport::Dependencies.log(MESSAGE) + end + end + + test "does not log if the logger is nil and verbose is true" do + with_settings(nil, true) do + assert_nil ActiveSupport::Dependencies.log(MESSAGE) + end + end + + test "does not log if the logger is set and verbose is false" do + with_settings(fake_logger, false) do + assert_nil ActiveSupport::Dependencies.log(MESSAGE) + end + end + + test "logs if the logger is set and verbose is true" do + with_settings(fake_logger, true) do + assert_equal "autoloading: #{MESSAGE}", ActiveSupport::Dependencies.log(MESSAGE) + end + end +end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 9456bb8753..70dec6b3d2 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -150,6 +150,18 @@ class SafeBufferTest < ActiveSupport::TestCase assert_not y.html_safe?, "should not be safe" end + test "Should continue safe on slice" do + x = "<div>foo</div>".html_safe + + assert_predicate x, :html_safe? + + # getting a slice of it + y = x[0..-1] + + # should still be safe + assert_predicate y, :html_safe? + end + test "Should work with interpolation (array argument)" do x = "foo %s bar".html_safe % ["qux"] assert_equal "foo qux bar", x diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index 9c2c635f43..8c47f2cdc7 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -186,4 +186,8 @@ class TimeTravelTest < ActiveSupport::TestCase assert_operator expected_time.to_s(:db), :<, Time.now.to_s(:db) end + + def test_time_helper_unfreeze_time + assert_equal method(:travel_back), method(:unfreeze_time) + end end diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 0307e06fd9..516b643cb8 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,7 @@ +* New section _Troubleshooting_ in the _Autoloading and Reloading Constants_ guide. + + *Xavier Noria* + * Rails 6 requires Ruby 2.4.1 or newer. *Jeremy Daer* diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js index e39ac239cd..a37f5d1927 100644 --- a/guides/assets/javascripts/guides.js +++ b/guides/assets/javascripts/guides.js @@ -19,7 +19,18 @@ return elem; } - document.addEventListener("DOMContentLoaded", function() { + // For old browsers + this.each = function(node, callback) { + var array = Array.prototype.slice.call(node); + for(var i = 0; i < array.length; i++) callback(array[i]); + } + + // Viewable on local + if (window.location.protocol === "file:") Turbolinks.supported = false; + + document.addEventListener("turbolinks:load", function() { + window.SyntaxHighlighter.highlight({ "auto-links": false }); + var guidesMenu = document.getElementById("guidesMenu"); var guides = document.getElementById("guides"); @@ -28,12 +39,22 @@ guides.classList.toggle("visible"); }); + each(document.querySelectorAll("#guides a"), function(element) { + element.addEventListener("click", function(e) { + guides.classList.toggle("visible"); + }); + }); + var guidesIndexItem = document.querySelector("select.guides-index-item"); var currentGuidePath = window.location.pathname; guidesIndexItem.value = currentGuidePath.substring(currentGuidePath.lastIndexOf("/") + 1); guidesIndexItem.addEventListener("change", function(e) { - window.location = e.target.value; + if (Turbolinks.supported) { + Turbolinks.visit(e.target.value); + } else { + window.location = e.target.value; + } }); var moreInfoButton = document.querySelector(".more-info-button"); diff --git a/guides/assets/javascripts/responsive-tables.js b/guides/assets/javascripts/responsive-tables.js index 24906dddeb..1c0f28c993 100644 --- a/guides/assets/javascripts/responsive-tables.js +++ b/guides/assets/javascripts/responsive-tables.js @@ -3,16 +3,6 @@ var switched = false; - // For old browsers - var each = function(node, callback) { - var array = Array.prototype.slice.call(node); - for(var i = 0; i < array.length; i++) callback(array[i]); - } - - each(document.querySelectorAll(":not(.syntaxhighlighter)>table"), function(element) { - element.classList.add("responsive"); - }); - var updateTables = function() { if (document.documentElement.clientWidth < 767 && !switched) { switched = true; @@ -23,7 +13,13 @@ } } - document.addEventListener("DOMContentLoaded", updateTables); + document.addEventListener("turbolinks:load", function() { + each(document.querySelectorAll(":not(.syntaxhighlighter)>table"), function(element) { + element.classList.add("responsive"); + }); + updateTables(); + }); + window.addEventListener("resize", updateTables); var splitTable = function(original) { diff --git a/guides/assets/javascripts/turbolinks.js b/guides/assets/javascripts/turbolinks.js new file mode 100644 index 0000000000..686283c7f0 --- /dev/null +++ b/guides/assets/javascripts/turbolinks.js @@ -0,0 +1,6 @@ +/* +Turbolinks 5.1.1 +Copyright © 2018 Basecamp, LLC + */ +(function(){var t=this;(function(){(function(){this.Turbolinks={supported:function(){return null!=window.history.pushState&&null!=window.requestAnimationFrame&&null!=window.addEventListener}(),visit:function(t,r){return e.controller.visit(t,r)},clearCache:function(){return e.controller.clearCache()},setProgressBarDelay:function(t){return e.controller.setProgressBarDelay(t)}}}).call(this)}).call(t);var e=t.Turbolinks;(function(){(function(){var t,r,n,o=[].slice;e.copyObject=function(t){var e,r,n;r={};for(e in t)n=t[e],r[e]=n;return r},e.closest=function(e,r){return t.call(e,r)},t=function(){var t,e;return t=document.documentElement,null!=(e=t.closest)?e:function(t){var e;for(e=this;e;){if(e.nodeType===Node.ELEMENT_NODE&&r.call(e,t))return e;e=e.parentNode}}}(),e.defer=function(t){return setTimeout(t,1)},e.throttle=function(t){var e;return e=null,function(){var r;return r=1<=arguments.length?o.call(arguments,0):[],null!=e?e:e=requestAnimationFrame(function(n){return function(){return e=null,t.apply(n,r)}}(this))}},e.dispatch=function(t,e){var r,o,i,s,a,u;return a=null!=e?e:{},u=a.target,r=a.cancelable,o=a.data,i=document.createEvent("Events"),i.initEvent(t,!0,r===!0),i.data=null!=o?o:{},i.cancelable&&!n&&(s=i.preventDefault,i.preventDefault=function(){return this.defaultPrevented||Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}}),s.call(this)}),(null!=u?u:document).dispatchEvent(i),i},n=function(){var t;return t=document.createEvent("Events"),t.initEvent("test",!0,!0),t.preventDefault(),t.defaultPrevented}(),e.match=function(t,e){return r.call(t,e)},r=function(){var t,e,r,n;return t=document.documentElement,null!=(e=null!=(r=null!=(n=t.matchesSelector)?n:t.webkitMatchesSelector)?r:t.msMatchesSelector)?e:t.mozMatchesSelector}(),e.uuid=function(){var t,e,r;for(r="",t=e=1;36>=e;t=++e)r+=9===t||14===t||19===t||24===t?"-":15===t?"4":20===t?(Math.floor(4*Math.random())+8).toString(16):Math.floor(15*Math.random()).toString(16);return r}}).call(this),function(){e.Location=function(){function t(t){var e,r;null==t&&(t=""),r=document.createElement("a"),r.href=t.toString(),this.absoluteURL=r.href,e=r.hash.length,2>e?this.requestURL=this.absoluteURL:(this.requestURL=this.absoluteURL.slice(0,-e),this.anchor=r.hash.slice(1))}var e,r,n,o;return t.wrap=function(t){return t instanceof this?t:new this(t)},t.prototype.getOrigin=function(){return this.absoluteURL.split("/",3).join("/")},t.prototype.getPath=function(){var t,e;return null!=(t=null!=(e=this.requestURL.match(/\/\/[^\/]*(\/[^?;]*)/))?e[1]:void 0)?t:"/"},t.prototype.getPathComponents=function(){return this.getPath().split("/").slice(1)},t.prototype.getLastPathComponent=function(){return this.getPathComponents().slice(-1)[0]},t.prototype.getExtension=function(){var t,e;return null!=(t=null!=(e=this.getLastPathComponent().match(/\.[^.]*$/))?e[0]:void 0)?t:""},t.prototype.isHTML=function(){return this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/)},t.prototype.isPrefixedBy=function(t){var e;return e=r(t),this.isEqualTo(t)||o(this.absoluteURL,e)},t.prototype.isEqualTo=function(t){return this.absoluteURL===(null!=t?t.absoluteURL:void 0)},t.prototype.toCacheKey=function(){return this.requestURL},t.prototype.toJSON=function(){return this.absoluteURL},t.prototype.toString=function(){return this.absoluteURL},t.prototype.valueOf=function(){return this.absoluteURL},r=function(t){return e(t.getOrigin()+t.getPath())},e=function(t){return n(t,"/")?t:t+"/"},o=function(t,e){return t.slice(0,e.length)===e},n=function(t,e){return t.slice(-e.length)===e},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.HttpRequest=function(){function r(r,n,o){this.delegate=r,this.requestCanceled=t(this.requestCanceled,this),this.requestTimedOut=t(this.requestTimedOut,this),this.requestFailed=t(this.requestFailed,this),this.requestLoaded=t(this.requestLoaded,this),this.requestProgressed=t(this.requestProgressed,this),this.url=e.Location.wrap(n).requestURL,this.referrer=e.Location.wrap(o).absoluteURL,this.createXHR()}return r.NETWORK_FAILURE=0,r.TIMEOUT_FAILURE=-1,r.timeout=60,r.prototype.send=function(){var t;return this.xhr&&!this.sent?(this.notifyApplicationBeforeRequestStart(),this.setProgress(0),this.xhr.send(),this.sent=!0,"function"==typeof(t=this.delegate).requestStarted?t.requestStarted():void 0):void 0},r.prototype.cancel=function(){return this.xhr&&this.sent?this.xhr.abort():void 0},r.prototype.requestProgressed=function(t){return t.lengthComputable?this.setProgress(t.loaded/t.total):void 0},r.prototype.requestLoaded=function(){return this.endRequest(function(t){return function(){var e;return 200<=(e=t.xhr.status)&&300>e?t.delegate.requestCompletedWithResponse(t.xhr.responseText,t.xhr.getResponseHeader("Turbolinks-Location")):(t.failed=!0,t.delegate.requestFailedWithStatusCode(t.xhr.status,t.xhr.responseText))}}(this))},r.prototype.requestFailed=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.NETWORK_FAILURE)}}(this))},r.prototype.requestTimedOut=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.TIMEOUT_FAILURE)}}(this))},r.prototype.requestCanceled=function(){return this.endRequest()},r.prototype.notifyApplicationBeforeRequestStart=function(){return e.dispatch("turbolinks:request-start",{data:{url:this.url,xhr:this.xhr}})},r.prototype.notifyApplicationAfterRequestEnd=function(){return e.dispatch("turbolinks:request-end",{data:{url:this.url,xhr:this.xhr}})},r.prototype.createXHR=function(){return this.xhr=new XMLHttpRequest,this.xhr.open("GET",this.url,!0),this.xhr.timeout=1e3*this.constructor.timeout,this.xhr.setRequestHeader("Accept","text/html, application/xhtml+xml"),this.xhr.setRequestHeader("Turbolinks-Referrer",this.referrer),this.xhr.onprogress=this.requestProgressed,this.xhr.onload=this.requestLoaded,this.xhr.onerror=this.requestFailed,this.xhr.ontimeout=this.requestTimedOut,this.xhr.onabort=this.requestCanceled},r.prototype.endRequest=function(t){return this.xhr?(this.notifyApplicationAfterRequestEnd(),null!=t&&t.call(this),this.destroy()):void 0},r.prototype.setProgress=function(t){var e;return this.progress=t,"function"==typeof(e=this.delegate).requestProgressed?e.requestProgressed(this.progress):void 0},r.prototype.destroy=function(){var t;return this.setProgress(1),"function"==typeof(t=this.delegate).requestFinished&&t.requestFinished(),this.delegate=null,this.xhr=null},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.ProgressBar=function(){function e(){this.trickle=t(this.trickle,this),this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement()}var r;return r=300,e.defaultCSS=".turbolinks-progress-bar {\n position: fixed;\n display: block;\n top: 0;\n left: 0;\n height: 3px;\n background: #0076ff;\n z-index: 9999;\n transition: width "+r+"ms ease-out, opacity "+r/2+"ms "+r/2+"ms ease-in;\n transform: translate3d(0, 0, 0);\n}",e.prototype.show=function(){return this.visible?void 0:(this.visible=!0,this.installStylesheetElement(),this.installProgressElement(),this.startTrickling())},e.prototype.hide=function(){return this.visible&&!this.hiding?(this.hiding=!0,this.fadeProgressElement(function(t){return function(){return t.uninstallProgressElement(),t.stopTrickling(),t.visible=!1,t.hiding=!1}}(this))):void 0},e.prototype.setValue=function(t){return this.value=t,this.refresh()},e.prototype.installStylesheetElement=function(){return document.head.insertBefore(this.stylesheetElement,document.head.firstChild)},e.prototype.installProgressElement=function(){return this.progressElement.style.width=0,this.progressElement.style.opacity=1,document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()},e.prototype.fadeProgressElement=function(t){return this.progressElement.style.opacity=0,setTimeout(t,1.5*r)},e.prototype.uninstallProgressElement=function(){return this.progressElement.parentNode?document.documentElement.removeChild(this.progressElement):void 0},e.prototype.startTrickling=function(){return null!=this.trickleInterval?this.trickleInterval:this.trickleInterval=setInterval(this.trickle,r)},e.prototype.stopTrickling=function(){return clearInterval(this.trickleInterval),this.trickleInterval=null},e.prototype.trickle=function(){return this.setValue(this.value+Math.random()/100)},e.prototype.refresh=function(){return requestAnimationFrame(function(t){return function(){return t.progressElement.style.width=10+90*t.value+"%"}}(this))},e.prototype.createStylesheetElement=function(){var t;return t=document.createElement("style"),t.type="text/css",t.textContent=this.constructor.defaultCSS,t},e.prototype.createProgressElement=function(){var t;return t=document.createElement("div"),t.className="turbolinks-progress-bar",t},e}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.BrowserAdapter=function(){function r(r){this.controller=r,this.showProgressBar=t(this.showProgressBar,this),this.progressBar=new e.ProgressBar}var n,o,i;return i=e.HttpRequest,n=i.NETWORK_FAILURE,o=i.TIMEOUT_FAILURE,r.prototype.visitProposedToLocationWithAction=function(t,e){return this.controller.startVisitToLocationWithAction(t,e)},r.prototype.visitStarted=function(t){return t.issueRequest(),t.changeHistory(),t.loadCachedSnapshot()},r.prototype.visitRequestStarted=function(t){return this.progressBar.setValue(0),t.hasCachedSnapshot()||"restore"!==t.action?this.showProgressBarAfterDelay():this.showProgressBar()},r.prototype.visitRequestProgressed=function(t){return this.progressBar.setValue(t.progress)},r.prototype.visitRequestCompleted=function(t){return t.loadResponse()},r.prototype.visitRequestFailedWithStatusCode=function(t,e){switch(e){case n:case o:return this.reload();default:return t.loadResponse()}},r.prototype.visitRequestFinished=function(t){return this.hideProgressBar()},r.prototype.visitCompleted=function(t){return t.followRedirect()},r.prototype.pageInvalidated=function(){return this.reload()},r.prototype.showProgressBarAfterDelay=function(){return this.progressBarTimeout=setTimeout(this.showProgressBar,this.controller.progressBarDelay)},r.prototype.showProgressBar=function(){return this.progressBar.show()},r.prototype.hideProgressBar=function(){return this.progressBar.hide(),clearTimeout(this.progressBarTimeout)},r.prototype.reload=function(){return window.location.reload()},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.History=function(){function r(e){this.delegate=e,this.onPageLoad=t(this.onPageLoad,this),this.onPopState=t(this.onPopState,this)}return r.prototype.start=function(){return this.started?void 0:(addEventListener("popstate",this.onPopState,!1),addEventListener("load",this.onPageLoad,!1),this.started=!0)},r.prototype.stop=function(){return this.started?(removeEventListener("popstate",this.onPopState,!1),removeEventListener("load",this.onPageLoad,!1),this.started=!1):void 0},r.prototype.push=function(t,r){return t=e.Location.wrap(t),this.update("push",t,r)},r.prototype.replace=function(t,r){return t=e.Location.wrap(t),this.update("replace",t,r)},r.prototype.onPopState=function(t){var r,n,o,i;return this.shouldHandlePopState()&&(i=null!=(n=t.state)?n.turbolinks:void 0)?(r=e.Location.wrap(window.location),o=i.restorationIdentifier,this.delegate.historyPoppedToLocationWithRestorationIdentifier(r,o)):void 0},r.prototype.onPageLoad=function(t){return e.defer(function(t){return function(){return t.pageLoaded=!0}}(this))},r.prototype.shouldHandlePopState=function(){return this.pageIsLoaded()},r.prototype.pageIsLoaded=function(){return this.pageLoaded||"complete"===document.readyState},r.prototype.update=function(t,e,r){var n;return n={turbolinks:{restorationIdentifier:r}},history[t+"State"](n,null,e)},r}()}.call(this),function(){e.Snapshot=function(){function t(t){var e,r;r=t.head,e=t.body,this.head=null!=r?r:document.createElement("head"),this.body=null!=e?e:document.createElement("body")}return t.wrap=function(t){return t instanceof this?t:this.fromHTML(t)},t.fromHTML=function(t){var e;return e=document.createElement("html"),e.innerHTML=t,this.fromElement(e)},t.fromElement=function(t){return new this({head:t.querySelector("head"),body:t.querySelector("body")})},t.prototype.clone=function(){return new t({head:this.head.cloneNode(!0),body:this.body.cloneNode(!0)})},t.prototype.getRootLocation=function(){var t,r;return r=null!=(t=this.getSetting("root"))?t:"/",new e.Location(r)},t.prototype.getCacheControlValue=function(){return this.getSetting("cache-control")},t.prototype.getElementForAnchor=function(t){try{return this.body.querySelector("[id='"+t+"'], a[name='"+t+"']")}catch(e){}},t.prototype.hasAnchor=function(t){return null!=this.getElementForAnchor(t)},t.prototype.isPreviewable=function(){return"no-preview"!==this.getCacheControlValue()},t.prototype.isCacheable=function(){return"no-cache"!==this.getCacheControlValue()},t.prototype.isVisitable=function(){return"reload"!==this.getSetting("visit-control")},t.prototype.getSetting=function(t){var e,r;return r=this.head.querySelectorAll("meta[name='turbolinks-"+t+"']"),e=r[r.length-1],null!=e?e.getAttribute("content"):void 0},t}()}.call(this),function(){var t=[].slice;e.Renderer=function(){function e(){}var r;return e.render=function(){var e,r,n,o;return n=arguments[0],r=arguments[1],e=3<=arguments.length?t.call(arguments,2):[],o=function(t,e,r){r.prototype=t.prototype;var n=new r,o=t.apply(n,e);return Object(o)===o?o:n}(this,e,function(){}),o.delegate=n,o.render(r),o},e.prototype.renderView=function(t){return this.delegate.viewWillRender(this.newBody),t(),this.delegate.viewRendered(this.newBody)},e.prototype.invalidateView=function(){return this.delegate.viewInvalidated()},e.prototype.createScriptElement=function(t){var e;return"false"===t.getAttribute("data-turbolinks-eval")?t:(e=document.createElement("script"),e.textContent=t.textContent,e.async=!1,r(e,t),e)},r=function(t,e){var r,n,o,i,s,a,u;for(i=e.attributes,a=[],r=0,n=i.length;n>r;r++)s=i[r],o=s.name,u=s.value,a.push(t.setAttribute(o,u));return a},e}()}.call(this),function(){e.HeadDetails=function(){function t(t){var e,r,i,s,a,u,l;for(this.element=t,this.elements={},l=this.element.childNodes,s=0,u=l.length;u>s;s++)i=l[s],i.nodeType===Node.ELEMENT_NODE&&(a=i.outerHTML,r=null!=(e=this.elements)[a]?e[a]:e[a]={type:o(i),tracked:n(i),elements:[]},r.elements.push(i))}var e,r,n,o;return t.prototype.hasElementWithKey=function(t){return t in this.elements},t.prototype.getTrackedElementSignature=function(){var t,e;return function(){var r,n;r=this.elements,n=[];for(t in r)e=r[t].tracked,e&&n.push(t);return n}.call(this).join("")},t.prototype.getScriptElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("script",t)},t.prototype.getStylesheetElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("stylesheet",t)},t.prototype.getElementsMatchingTypeNotInDetails=function(t,e){var r,n,o,i,s,a;o=this.elements,s=[];for(n in o)i=o[n],a=i.type,r=i.elements,a!==t||e.hasElementWithKey(n)||s.push(r[0]);return s},t.prototype.getProvisionalElements=function(){var t,e,r,n,o,i,s;r=[],n=this.elements;for(e in n)o=n[e],s=o.type,i=o.tracked,t=o.elements,null!=s||i?t.length>1&&r.push.apply(r,t.slice(1)):r.push.apply(r,t);return r},o=function(t){return e(t)?"script":r(t)?"stylesheet":void 0},n=function(t){return"reload"===t.getAttribute("data-turbolinks-track")},e=function(t){var e;return e=t.tagName.toLowerCase(),"script"===e},r=function(t){var e;return e=t.tagName.toLowerCase(),"style"===e||"link"===e&&"stylesheet"===t.getAttribute("rel")},t}()}.call(this),function(){var t=function(t,e){function n(){this.constructor=t}for(var o in e)r.call(e,o)&&(t[o]=e[o]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},r={}.hasOwnProperty;e.SnapshotRenderer=function(r){function n(t,r,n){this.currentSnapshot=t,this.newSnapshot=r,this.isPreview=n,this.currentHeadDetails=new e.HeadDetails(this.currentSnapshot.head),this.newHeadDetails=new e.HeadDetails(this.newSnapshot.head),this.newBody=this.newSnapshot.body}return t(n,r),n.prototype.render=function(t){return this.shouldRender()?(this.mergeHead(),this.renderView(function(e){return function(){return e.replaceBody(),e.isPreview||e.focusFirstAutofocusableElement(),t()}}(this))):this.invalidateView()},n.prototype.mergeHead=function(){return this.copyNewHeadStylesheetElements(),this.copyNewHeadScriptElements(),this.removeCurrentHeadProvisionalElements(),this.copyNewHeadProvisionalElements()},n.prototype.replaceBody=function(){return this.activateBodyScriptElements(),this.importBodyPermanentElements(),this.assignNewBody()},n.prototype.shouldRender=function(){return this.newSnapshot.isVisitable()&&this.trackedElementsAreIdentical()},n.prototype.trackedElementsAreIdentical=function(){return this.currentHeadDetails.getTrackedElementSignature()===this.newHeadDetails.getTrackedElementSignature()},n.prototype.copyNewHeadStylesheetElements=function(){var t,e,r,n,o;for(n=this.getNewHeadStylesheetElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},n.prototype.copyNewHeadScriptElements=function(){var t,e,r,n,o;for(n=this.getNewHeadScriptElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(this.createScriptElement(t)));return o},n.prototype.removeCurrentHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getCurrentHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.removeChild(t));return o},n.prototype.copyNewHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getNewHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},n.prototype.importBodyPermanentElements=function(){var t,e,r,n,o,i;for(n=this.getNewBodyPermanentElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],(t=this.findCurrentBodyPermanentElement(o))?i.push(o.parentNode.replaceChild(t,o)):i.push(void 0);return i},n.prototype.activateBodyScriptElements=function(){var t,e,r,n,o,i;for(n=this.getNewBodyScriptElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],t=this.createScriptElement(o),i.push(o.parentNode.replaceChild(t,o));return i},n.prototype.assignNewBody=function(){return document.body=this.newBody},n.prototype.focusFirstAutofocusableElement=function(){var t;return null!=(t=this.findFirstAutofocusableElement())?t.focus():void 0},n.prototype.getNewHeadStylesheetElements=function(){return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails)},n.prototype.getNewHeadScriptElements=function(){return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails)},n.prototype.getCurrentHeadProvisionalElements=function(){return this.currentHeadDetails.getProvisionalElements()},n.prototype.getNewHeadProvisionalElements=function(){return this.newHeadDetails.getProvisionalElements()},n.prototype.getNewBodyPermanentElements=function(){return this.newBody.querySelectorAll("[id][data-turbolinks-permanent]")},n.prototype.findCurrentBodyPermanentElement=function(t){return document.body.querySelector("#"+t.id+"[data-turbolinks-permanent]")},n.prototype.getNewBodyScriptElements=function(){return this.newBody.querySelectorAll("script")},n.prototype.findFirstAutofocusableElement=function(){return document.body.querySelector("[autofocus]")},n}(e.Renderer)}.call(this),function(){var t=function(t,e){function n(){this.constructor=t}for(var o in e)r.call(e,o)&&(t[o]=e[o]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},r={}.hasOwnProperty;e.ErrorRenderer=function(e){function r(t){this.html=t}return t(r,e),r.prototype.render=function(t){return this.renderView(function(e){return function(){return e.replaceDocumentHTML(),e.activateBodyScriptElements(),t()}}(this))},r.prototype.replaceDocumentHTML=function(){return document.documentElement.innerHTML=this.html},r.prototype.activateBodyScriptElements=function(){var t,e,r,n,o,i;for(n=this.getScriptElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],t=this.createScriptElement(o),i.push(o.parentNode.replaceChild(t,o));return i},r.prototype.getScriptElements=function(){return document.documentElement.querySelectorAll("script")},r}(e.Renderer)}.call(this),function(){e.View=function(){function t(t){this.delegate=t,this.element=document.documentElement}return t.prototype.getRootLocation=function(){return this.getSnapshot().getRootLocation()},t.prototype.getElementForAnchor=function(t){return this.getSnapshot().getElementForAnchor(t)},t.prototype.getSnapshot=function(){return e.Snapshot.fromElement(this.element)},t.prototype.render=function(t,e){var r,n,o;return o=t.snapshot,r=t.error,n=t.isPreview,this.markAsPreview(n),null!=o?this.renderSnapshot(o,n,e):this.renderError(r,e)},t.prototype.markAsPreview=function(t){return t?this.element.setAttribute("data-turbolinks-preview",""):this.element.removeAttribute("data-turbolinks-preview")},t.prototype.renderSnapshot=function(t,r,n){return e.SnapshotRenderer.render(this.delegate,n,this.getSnapshot(),e.Snapshot.wrap(t),r)},t.prototype.renderError=function(t,r){return e.ErrorRenderer.render(this.delegate,r,t)},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.ScrollManager=function(){function r(r){this.delegate=r,this.onScroll=t(this.onScroll,this),this.onScroll=e.throttle(this.onScroll)}return r.prototype.start=function(){return this.started?void 0:(addEventListener("scroll",this.onScroll,!1),this.onScroll(),this.started=!0)},r.prototype.stop=function(){return this.started?(removeEventListener("scroll",this.onScroll,!1),this.started=!1):void 0},r.prototype.scrollToElement=function(t){return t.scrollIntoView()},r.prototype.scrollToPosition=function(t){var e,r;return e=t.x,r=t.y,window.scrollTo(e,r)},r.prototype.onScroll=function(t){return this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})},r.prototype.updatePosition=function(t){var e;return this.position=t,null!=(e=this.delegate)?e.scrollPositionChanged(this.position):void 0},r}()}.call(this),function(){e.SnapshotCache=function(){function t(t){this.size=t,this.keys=[],this.snapshots={}}var r;return t.prototype.has=function(t){var e;return e=r(t),e in this.snapshots},t.prototype.get=function(t){var e;if(this.has(t))return e=this.read(t),this.touch(t),e},t.prototype.put=function(t,e){return this.write(t,e),this.touch(t),e},t.prototype.read=function(t){var e;return e=r(t),this.snapshots[e]},t.prototype.write=function(t,e){var n;return n=r(t),this.snapshots[n]=e},t.prototype.touch=function(t){var e,n;return n=r(t),e=this.keys.indexOf(n),e>-1&&this.keys.splice(e,1),this.keys.unshift(n),this.trim()},t.prototype.trim=function(){var t,e,r,n,o;for(n=this.keys.splice(this.size),o=[],t=0,r=n.length;r>t;t++)e=n[t],o.push(delete this.snapshots[e]);return o},r=function(t){return e.Location.wrap(t).toCacheKey()},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.Visit=function(){function r(r,n,o){this.controller=r,this.action=o,this.performScroll=t(this.performScroll,this),this.identifier=e.uuid(),this.location=e.Location.wrap(n),this.adapter=this.controller.adapter,this.state="initialized",this.timingMetrics={}}var n;return r.prototype.start=function(){return"initialized"===this.state?(this.recordTimingMetric("visitStart"),this.state="started",this.adapter.visitStarted(this)):void 0},r.prototype.cancel=function(){var t;return"started"===this.state?(null!=(t=this.request)&&t.cancel(),this.cancelRender(),this.state="canceled"):void 0},r.prototype.complete=function(){var t;return"started"===this.state?(this.recordTimingMetric("visitEnd"),this.state="completed","function"==typeof(t=this.adapter).visitCompleted&&t.visitCompleted(this),this.controller.visitCompleted(this)):void 0},r.prototype.fail=function(){var t;return"started"===this.state?(this.state="failed","function"==typeof(t=this.adapter).visitFailed?t.visitFailed(this):void 0):void 0},r.prototype.changeHistory=function(){var t,e;return this.historyChanged?void 0:(t=this.location.isEqualTo(this.referrer)?"replace":this.action,e=n(t),this.controller[e](this.location,this.restorationIdentifier),this.historyChanged=!0)},r.prototype.issueRequest=function(){return this.shouldIssueRequest()&&null==this.request?(this.progress=0,this.request=new e.HttpRequest(this,this.location,this.referrer),this.request.send()):void 0},r.prototype.getCachedSnapshot=function(){var t;return!(t=this.controller.getCachedSnapshotForLocation(this.location))||null!=this.location.anchor&&!t.hasAnchor(this.location.anchor)||"restore"!==this.action&&!t.isPreviewable()?void 0:t},r.prototype.hasCachedSnapshot=function(){return null!=this.getCachedSnapshot()},r.prototype.loadCachedSnapshot=function(){var t,e;return(e=this.getCachedSnapshot())?(t=this.shouldIssueRequest(),this.render(function(){var r;return this.cacheSnapshot(),this.controller.render({snapshot:e,isPreview:t},this.performScroll),"function"==typeof(r=this.adapter).visitRendered&&r.visitRendered(this),t?void 0:this.complete()})):void 0},r.prototype.loadResponse=function(){return null!=this.response?this.render(function(){var t,e;return this.cacheSnapshot(),this.request.failed?(this.controller.render({error:this.response},this.performScroll),"function"==typeof(t=this.adapter).visitRendered&&t.visitRendered(this),this.fail()):(this.controller.render({snapshot:this.response},this.performScroll),"function"==typeof(e=this.adapter).visitRendered&&e.visitRendered(this),this.complete())}):void 0},r.prototype.followRedirect=function(){return this.redirectedToLocation&&!this.followedRedirect?(this.location=this.redirectedToLocation,this.controller.replaceHistoryWithLocationAndRestorationIdentifier(this.redirectedToLocation,this.restorationIdentifier),this.followedRedirect=!0):void 0},r.prototype.requestStarted=function(){var t;return this.recordTimingMetric("requestStart"),"function"==typeof(t=this.adapter).visitRequestStarted?t.visitRequestStarted(this):void 0},r.prototype.requestProgressed=function(t){var e;return this.progress=t,"function"==typeof(e=this.adapter).visitRequestProgressed?e.visitRequestProgressed(this):void 0},r.prototype.requestCompletedWithResponse=function(t,r){return this.response=t,null!=r&&(this.redirectedToLocation=e.Location.wrap(r)),this.adapter.visitRequestCompleted(this)},r.prototype.requestFailedWithStatusCode=function(t,e){return this.response=e,this.adapter.visitRequestFailedWithStatusCode(this,t)},r.prototype.requestFinished=function(){var t;return this.recordTimingMetric("requestEnd"),"function"==typeof(t=this.adapter).visitRequestFinished?t.visitRequestFinished(this):void 0},r.prototype.performScroll=function(){return this.scrolled?void 0:("restore"===this.action?this.scrollToRestoredPosition()||this.scrollToTop():this.scrollToAnchor()||this.scrollToTop(),this.scrolled=!0)},r.prototype.scrollToRestoredPosition=function(){var t,e;return t=null!=(e=this.restorationData)?e.scrollPosition:void 0,null!=t?(this.controller.scrollToPosition(t),!0):void 0},r.prototype.scrollToAnchor=function(){return null!=this.location.anchor?(this.controller.scrollToAnchor(this.location.anchor),!0):void 0},r.prototype.scrollToTop=function(){return this.controller.scrollToPosition({x:0,y:0})},r.prototype.recordTimingMetric=function(t){var e;return null!=(e=this.timingMetrics)[t]?e[t]:e[t]=(new Date).getTime()},r.prototype.getTimingMetrics=function(){return e.copyObject(this.timingMetrics)},n=function(t){switch(t){case"replace":return"replaceHistoryWithLocationAndRestorationIdentifier";case"advance":case"restore":return"pushHistoryWithLocationAndRestorationIdentifier"}},r.prototype.shouldIssueRequest=function(){return"restore"===this.action?!this.hasCachedSnapshot():!0},r.prototype.cacheSnapshot=function(){return this.snapshotCached?void 0:(this.controller.cacheSnapshot(),this.snapshotCached=!0)},r.prototype.render=function(t){return this.cancelRender(),this.frame=requestAnimationFrame(function(e){return function(){return e.frame=null,t.call(e)}}(this))},r.prototype.cancelRender=function(){return this.frame?cancelAnimationFrame(this.frame):void 0},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.Controller=function(){function r(){this.clickBubbled=t(this.clickBubbled,this),this.clickCaptured=t(this.clickCaptured,this),this.pageLoaded=t(this.pageLoaded,this),this.history=new e.History(this),this.view=new e.View(this),this.scrollManager=new e.ScrollManager(this),this.restorationData={},this.clearCache(),this.setProgressBarDelay(500)}return r.prototype.start=function(){return e.supported&&!this.started?(addEventListener("click",this.clickCaptured,!0),addEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.start(),this.startHistory(),this.started=!0,this.enabled=!0):void 0},r.prototype.disable=function(){return this.enabled=!1},r.prototype.stop=function(){return this.started?(removeEventListener("click",this.clickCaptured,!0),removeEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.stop(),this.stopHistory(),this.started=!1):void 0},r.prototype.clearCache=function(){return this.cache=new e.SnapshotCache(10)},r.prototype.visit=function(t,r){var n,o;return null==r&&(r={}),t=e.Location.wrap(t),this.applicationAllowsVisitingLocation(t)?this.locationIsVisitable(t)?(n=null!=(o=r.action)?o:"advance",this.adapter.visitProposedToLocationWithAction(t,n)):window.location=t:void 0},r.prototype.startVisitToLocationWithAction=function(t,r,n){var o;return e.supported?(o=this.getRestorationDataForIdentifier(n),this.startVisit(t,r,{restorationData:o})):window.location=t},r.prototype.setProgressBarDelay=function(t){return this.progressBarDelay=t},r.prototype.startHistory=function(){return this.location=e.Location.wrap(window.location),this.restorationIdentifier=e.uuid(),this.history.start(),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.stopHistory=function(){return this.history.stop()},r.prototype.pushHistoryWithLocationAndRestorationIdentifier=function(t,r){return this.restorationIdentifier=r,this.location=e.Location.wrap(t),this.history.push(this.location,this.restorationIdentifier)},r.prototype.replaceHistoryWithLocationAndRestorationIdentifier=function(t,r){return this.restorationIdentifier=r,this.location=e.Location.wrap(t),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.historyPoppedToLocationWithRestorationIdentifier=function(t,r){var n;return this.restorationIdentifier=r,this.enabled?(n=this.getRestorationDataForIdentifier(this.restorationIdentifier),this.startVisit(t,"restore",{restorationIdentifier:this.restorationIdentifier,restorationData:n,historyChanged:!0}),this.location=e.Location.wrap(t)):this.adapter.pageInvalidated()},r.prototype.getCachedSnapshotForLocation=function(t){var e;return e=this.cache.get(t),e?e.clone():void 0},r.prototype.shouldCacheSnapshot=function(){return this.view.getSnapshot().isCacheable()},r.prototype.cacheSnapshot=function(){var t;return this.shouldCacheSnapshot()?(this.notifyApplicationBeforeCachingSnapshot(),t=this.view.getSnapshot(),this.cache.put(this.lastRenderedLocation,t.clone())):void 0},r.prototype.scrollToAnchor=function(t){var e;return(e=this.view.getElementForAnchor(t))?this.scrollToElement(e):this.scrollToPosition({x:0,y:0})},r.prototype.scrollToElement=function(t){return this.scrollManager.scrollToElement(t)},r.prototype.scrollToPosition=function(t){return this.scrollManager.scrollToPosition(t)},r.prototype.scrollPositionChanged=function(t){var e;return e=this.getCurrentRestorationData(),e.scrollPosition=t},r.prototype.render=function(t,e){return this.view.render(t,e)},r.prototype.viewInvalidated=function(){return this.adapter.pageInvalidated()},r.prototype.viewWillRender=function(t){return this.notifyApplicationBeforeRender(t)},r.prototype.viewRendered=function(){return this.lastRenderedLocation=this.currentVisit.location,this.notifyApplicationAfterRender()},r.prototype.pageLoaded=function(){return this.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()},r.prototype.clickCaptured=function(){return removeEventListener("click",this.clickBubbled,!1),addEventListener("click",this.clickBubbled,!1)},r.prototype.clickBubbled=function(t){var e,r,n;return this.enabled&&this.clickEventIsSignificant(t)&&(r=this.getVisitableLinkForNode(t.target))&&(n=this.getVisitableLocationForLink(r))&&this.applicationAllowsFollowingLinkToLocation(r,n)?(t.preventDefault(),e=this.getActionForLink(r), +this.visit(n,{action:e})):void 0},r.prototype.applicationAllowsFollowingLinkToLocation=function(t,e){var r;return r=this.notifyApplicationAfterClickingLinkToLocation(t,e),!r.defaultPrevented},r.prototype.applicationAllowsVisitingLocation=function(t){var e;return e=this.notifyApplicationBeforeVisitingLocation(t),!e.defaultPrevented},r.prototype.notifyApplicationAfterClickingLinkToLocation=function(t,r){return e.dispatch("turbolinks:click",{target:t,data:{url:r.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationBeforeVisitingLocation=function(t){return e.dispatch("turbolinks:before-visit",{data:{url:t.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationAfterVisitingLocation=function(t){return e.dispatch("turbolinks:visit",{data:{url:t.absoluteURL}})},r.prototype.notifyApplicationBeforeCachingSnapshot=function(){return e.dispatch("turbolinks:before-cache")},r.prototype.notifyApplicationBeforeRender=function(t){return e.dispatch("turbolinks:before-render",{data:{newBody:t}})},r.prototype.notifyApplicationAfterRender=function(){return e.dispatch("turbolinks:render")},r.prototype.notifyApplicationAfterPageLoad=function(t){return null==t&&(t={}),e.dispatch("turbolinks:load",{data:{url:this.location.absoluteURL,timing:t}})},r.prototype.startVisit=function(t,e,r){var n;return null!=(n=this.currentVisit)&&n.cancel(),this.currentVisit=this.createVisit(t,e,r),this.currentVisit.start(),this.notifyApplicationAfterVisitingLocation(t)},r.prototype.createVisit=function(t,r,n){var o,i,s,a,u;return i=null!=n?n:{},a=i.restorationIdentifier,s=i.restorationData,o=i.historyChanged,u=new e.Visit(this,t,r),u.restorationIdentifier=null!=a?a:e.uuid(),u.restorationData=e.copyObject(s),u.historyChanged=o,u.referrer=this.location,u},r.prototype.visitCompleted=function(t){return this.notifyApplicationAfterPageLoad(t.getTimingMetrics())},r.prototype.clickEventIsSignificant=function(t){return!(t.defaultPrevented||t.target.isContentEditable||t.which>1||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey)},r.prototype.getVisitableLinkForNode=function(t){return this.nodeIsVisitable(t)?e.closest(t,"a[href]:not([target]):not([download])"):void 0},r.prototype.getVisitableLocationForLink=function(t){var r;return r=new e.Location(t.getAttribute("href")),this.locationIsVisitable(r)?r:void 0},r.prototype.getActionForLink=function(t){var e;return null!=(e=t.getAttribute("data-turbolinks-action"))?e:"advance"},r.prototype.nodeIsVisitable=function(t){var r;return(r=e.closest(t,"[data-turbolinks]"))?"false"!==r.getAttribute("data-turbolinks"):!0},r.prototype.locationIsVisitable=function(t){return t.isPrefixedBy(this.view.getRootLocation())&&t.isHTML()},r.prototype.getCurrentRestorationData=function(){return this.getRestorationDataForIdentifier(this.restorationIdentifier)},r.prototype.getRestorationDataForIdentifier=function(t){var e;return null!=(e=this.restorationData)[t]?e[t]:e[t]={}},r}()}.call(this),function(){!function(){var t,e;if((t=e=document.currentScript)&&!e.hasAttribute("data-turbolinks-suppress-warning"))for(;t=t.parentNode;)if(t===document.body)return console.warn("You are loading Turbolinks from a <script> element inside the <body> element. This is probably not what you meant to do!\n\nLoad your application\u2019s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.\n\nFor more information, see: https://github.com/turbolinks/turbolinks#working-with-script-elements\n\n\u2014\u2014\nSuppress this warning by adding a `data-turbolinks-suppress-warning` attribute to: %s",e.outerHTML)}()}.call(this),function(){var t,r,n;e.start=function(){return r()?(null==e.controller&&(e.controller=t()),e.controller.start()):void 0},r=function(){return null==window.Turbolinks&&(window.Turbolinks=e),n()},t=function(){var t;return t=new e.Controller,t.adapter=new e.BrowserAdapter(t),t},n=function(){return window.Turbolinks===e},n()&&e.start()}.call(this)}).call(this),"object"==typeof module&&module.exports?module.exports=e:"function"==typeof define&&define.amd&&define(e)}).call(this); diff --git a/guides/assets/stylesheets/style.css b/guides/assets/stylesheets/style.css index 89b2ab885a..3dad5124f4 100644 --- a/guides/assets/stylesheets/style.css +++ b/guides/assets/stylesheets/style.css @@ -11,3 +11,4 @@ Import advanced style sheet @import url("reset.css"); @import url("main.css"); +@import url("turbolinks.css"); diff --git a/guides/assets/stylesheets/turbolinks.css b/guides/assets/stylesheets/turbolinks.css new file mode 100644 index 0000000000..5cb598cef2 --- /dev/null +++ b/guides/assets/stylesheets/turbolinks.css @@ -0,0 +1,3 @@ +.turbolinks-progress-bar { + background-color: #c52f24; +} diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 37cbf3f53d..406615f681 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -787,7 +787,7 @@ files (environment.rb, production.rb, etc...) |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i`.</li></ul>| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| |`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.| -|`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| +|`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing. If this value is `false`, `deliveries` array will not be populated even if delivery_method is :test.| |`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| |`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).| diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 8581817d71..69c6a6e414 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -319,17 +319,18 @@ Action Mailer ### deliver.action_mailer -| Key | Value | -| ------------- | -------------------------------------------- | -| `:mailer` | Name of the mailer class | -| `:message_id` | ID of the message, generated by the Mail gem | -| `:subject` | Subject of the mail | -| `:to` | To address(es) of the mail | -| `:from` | From address of the mail | -| `:bcc` | BCC addresses of the mail | -| `:cc` | CC addresses of the mail | -| `:date` | Date of the mail | -| `:mail` | The encoded form of the mail | +| Key | Value | +| --------------------- | ---------------------------------------------------- | +| `:mailer` | Name of the mailer class | +| `:message_id` | ID of the message, generated by the Mail gem | +| `:subject` | Subject of the mail | +| `:to` | To address(es) of the mail | +| `:from` | From address of the mail | +| `:bcc` | BCC addresses of the mail | +| `:cc` | CC addresses of the mail | +| `:date` | Date of the mail | +| `:mail` | The encoded form of the mail | +| `:perform_deliveries` | Whether delivery of this message is performed or not | ```ruby { @@ -339,7 +340,8 @@ Action Mailer to: ["users@rails.com", "dhh@rails.com"], from: ["me@rails.com"], date: Sat, 10 Mar 2012 14:18:09 +0100, - mail: "..." # omitted for brevity + mail: "...", # omitted for brevity + perform_deliveries: true } ``` diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index 6298651e4a..b3f923a017 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -1350,3 +1350,34 @@ With the [Spring](https://github.com/rails/spring) pre-loader (included with new Occasionally you may need to explicitly eager_load by using `Rails .application.eager_load!` in the setup of your tests -- this might occur if your [tests involve multithreading](https://stackoverflow.com/questions/25796409/in-rails-how-can-i-eager-load-all-code-before-a-specific-rspec-test). + +## Troubleshooting + +### Tracing Autoloads + +Active Support is able to report constants as they are autoloaded. To enable these traces in a Rails application, put the following two lines in some initializer: + +```ruby +ActiveSupport::Dependencies.logger = Rails.logger +ActiveSupport::Dependencies.verbose = true +``` + +### Where is a Given Autoload Triggered? + +If constant `Foo` is being autoloaded, and you'd like to know where is that autoload coming from, just throw + +```ruby +puts caller +``` + +at the top of `foo.rb` and inspect the printed stack trace. + +### Which Constants Have Been Autoloaded? + +At any given time, + +```ruby +ActiveSupport::Dependencies.autoloaded_constants +``` + +has the collection of constants that have been autoloaded so far. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 8c95187fa4..c2ec5b8994 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -954,7 +954,7 @@ The only way to explicitly not use the connection information in `ENV['DATABASE_ ``` $ cat config/database.yml development: - url: sqlite3://NOT_my_database + url: sqlite3:NOT_my_database $ echo $DATABASE_URL postgresql://localhost/my_database @@ -966,8 +966,8 @@ $ rails runner 'puts ActiveRecord::Base.configurations.inspect' #<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[ #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0 @env_name="development", @spec_name="primary", - @config={"adapter"=>"sqlite3", "database"=>"NOT_my_database", "host"=>"localhost"} - @url="sqlite3://NOT_my_database"> + @config={"adapter"=>"sqlite3", "database"=>"NOT_my_database"} + @url="sqlite3:NOT_my_database"> ] ``` diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 8fbd4d4ed4..dd9175e312 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -1,20 +1,19 @@ <!DOCTYPE html> - -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<html lang="en"> <head> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> -<meta name="viewport" content="width=device-width, initial-scale=1"/> - -<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title> -<link rel="stylesheet" type="text/css" href="stylesheets/style.css" /> -<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" /> - -<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" /> -<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" /> - -<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" /> - -<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" /> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title> + <link rel="stylesheet" type="text/css" href="stylesheets/style.css" data-turbolinks-track="reload"> + <link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print"> + <link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" data-turbolinks-track="reload"> + <link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" data-turbolinks-track="reload"> + <link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" data-turbolinks-track="reload"> + <link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" /> + <script src="javascripts/syntaxhighlighter.js" data-turbolinks-track="reload"></script> + <script src="javascripts/turbolinks.js" data-turbolinks-track="reload"></script> + <script src="javascripts/guides.js" data-turbolinks-track="reload"></script> + <script src="javascripts/responsive-tables.js" data-turbolinks-track="reload"></script> </head> <body class="guide"> <% if @edge %> @@ -122,9 +121,5 @@ <%= render 'license' %> </div> </div> - - <script type="text/javascript" src="javascripts/syntaxhighlighter.js"></script> - <script type="text/javascript" src="javascripts/guides.js"></script> - <script type="text/javascript" src="javascripts/responsive-tables.js"></script> </body> </html> diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2d7067885d..b916fda1cb 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -52,13 +52,13 @@ *Annie-Claude Côté* -* Don't generate unused files in `app:update` task +* Don't generate unused files in `app:update` task. - Skip the assets' initializer when sprockets isn't loaded. + Skip the assets' initializer when sprockets isn't loaded. - Skip `config/spring.rb` when spring isn't loaded. + Skip `config/spring.rb` when spring isn't loaded. - Skip yarn's contents when yarn integration isn't used. + Skip yarn's contents when yarn integration isn't used. *Tsukuru Tanimichi* diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt index 1dc508b14f..dda979555a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt @@ -11,7 +11,7 @@ # default: &default adapter: mysql2 - encoding: utf8 + encoding: utf8mb4 pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt index 9a8c4bf098..405642c850 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt @@ -4,21 +4,30 @@ $:.push File.expand_path("lib", __dir__) require "<%= namespaced_name %>/version" # Describe your gem and declare its dependencies: -Gem::Specification.new do |s| - s.name = "<%= name %>" - s.version = <%= camelized_modules %>::VERSION - s.authors = ["<%= author %>"] - s.email = ["<%= email %>"] - s.homepage = "TODO" - s.summary = "TODO: Summary of <%= camelized_modules %>." - s.description = "TODO: Description of <%= camelized_modules %>." - s.license = "MIT" +Gem::Specification.new do |spec| + spec.name = "<%= name %>" + spec.version = <%= camelized_modules %>::VERSION + spec.authors = ["<%= author %>"] + spec.email = ["<%= email %>"] + spec.homepage = "TODO" + spec.summary = "TODO: Summary of <%= camelized_modules %>." + spec.description = "TODO: Description of <%= camelized_modules %>." + spec.license = "MIT" - s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] + # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' + # to allow pushing to a single host or delete this section to allow pushing to any host. + if spec.respond_to?(:metadata) + spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" + else + raise "RubyGems 2.0 or newer is required to protect against " \ + "public gem pushes." + end - <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "<%= Array(rails_version_specifier).join('", "') %>" + spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] + + <%= '# ' if options.dev? || options.edge? -%>spec.add_dependency "rails", "<%= Array(rails_version_specifier).join('", "') %>" <% unless options[:skip_active_record] -%> - s.add_development_dependency "<%= gem_for_database[0] %>" + spec.add_development_dependency "<%= gem_for_database[0] %>" <% end -%> end diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake index 65af778a15..3a78de418a 100644 --- a/railties/lib/rails/tasks/annotations.rake +++ b/railties/lib/rails/tasks/annotations.rake @@ -2,20 +2,20 @@ require "rails/source_annotation_extractor" -task :notes do +task notes: :environment do Rails::SourceAnnotationExtractor::Annotation.notes_task_deprecation_warning Rails::Command.invoke :notes end namespace :notes do ["OPTIMIZE", "FIXME", "TODO"].each do |annotation| - task annotation.downcase.intern do + task annotation.downcase.intern => :environment do Rails::SourceAnnotationExtractor::Annotation.notes_task_deprecation_warning Rails::Command.invoke :notes, ["--annotations", annotation] end end - task :custom do + task custom: :environment do Rails::SourceAnnotationExtractor::Annotation.notes_task_deprecation_warning Rails::Command.invoke :notes, ["--annotations", ENV["ANNOTATION"]] end diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake index 8d75965294..716fb6a331 100644 --- a/railties/lib/rails/tasks/dev.rake +++ b/railties/lib/rails/tasks/dev.rake @@ -4,7 +4,7 @@ require "rails/command" require "active_support/deprecation" namespace :dev do - task :cache do + task cache: :environment do ActiveSupport::Deprecation.warn("Using `bin/rake dev:cache` is deprecated and will be removed in Rails 6.1. Use `bin/rails dev:cache` instead.\n") Rails::Command.invoke "dev:cache" end diff --git a/railties/lib/rails/tasks/initializers.rake b/railties/lib/rails/tasks/initializers.rake index 1fa8ca4f51..f108517d1d 100644 --- a/railties/lib/rails/tasks/initializers.rake +++ b/railties/lib/rails/tasks/initializers.rake @@ -3,7 +3,7 @@ require "rails/command" require "active_support/deprecation" -task :initializers do +task initializers: :environment do ActiveSupport::Deprecation.warn("Using `bin/rake initializers` is deprecated and will be removed in Rails 6.1. Use `bin/rails initializers` instead.\n") Rails::Command.invoke "initializers" end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c2699006f6..f5119b1931 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1996,6 +1996,15 @@ module ApplicationTests assert_equal false, ActionView::Template.finalize_compiled_template_methods end + test "ActiveRecord::Base.filter_attributes should equal to filter_parameters" do + app_file "config/initializers/filter_parameters_logging.rb", <<-RUBY + Rails.application.config.filter_parameters += [ :password, :credit_card_number ] + RUBY + app "development" + assert_equal [ :password, :credit_card_number ], Rails.application.config.filter_parameters + assert_equal [ :password, :credit_card_number ], ActiveRecord::Base.filter_attributes + end + private def force_lazy_load_hooks yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it. diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 4a14042cd3..29f8b1e3d9 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -109,7 +109,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase CODE system "#{app_path}/bin/rails runner 'Post.connection.create_table :posts'" - @master, @slave = PTY.open + @primary, @replica = PTY.open end def teardown @@ -117,19 +117,19 @@ class FullStackConsoleTest < ActiveSupport::TestCase end def write_prompt(command, expected_output = nil) - @master.puts command - assert_output command, @master - assert_output expected_output, @master if expected_output - assert_output "> ", @master + @primary.puts command + assert_output command, @primary + assert_output expected_output, @primary if expected_output + assert_output "> ", @primary end def spawn_console(options) Process.spawn( "#{app_path}/bin/rails console #{options}", - in: @slave, out: @slave, err: @slave + in: @replica, out: @replica, err: @replica ) - assert_output "> ", @master, 30 + assert_output "> ", @primary, 30 end def test_sandbox @@ -138,14 +138,14 @@ class FullStackConsoleTest < ActiveSupport::TestCase write_prompt "Post.count", "=> 0" write_prompt "Post.create" write_prompt "Post.count", "=> 1" - @master.puts "quit" + @primary.puts "quit" spawn_console("--sandbox") write_prompt "Post.count", "=> 0" write_prompt "Post.transaction { Post.create; raise }" write_prompt "Post.count", "=> 0" - @master.puts "quit" + @primary.puts "quit" end def test_environment_option_and_irb_option @@ -153,6 +153,6 @@ class FullStackConsoleTest < ActiveSupport::TestCase write_prompt "a = 1", "a = 1" write_prompt "puts Rails.env", "puts Rails.env\r\ntest" - @master.puts "quit" + @primary.puts "quit" end end diff --git a/railties/test/application/dbconsole_test.rb b/railties/test/application/dbconsole_test.rb index 8eb293c179..8c03fe4ac6 100644 --- a/railties/test/application/dbconsole_test.rb +++ b/railties/test/application/dbconsole_test.rb @@ -33,11 +33,11 @@ module ApplicationTests end RUBY - master, slave = PTY.open - spawn_dbconsole(slave) - assert_output("sqlite>", master) + primary, replica = PTY.open + spawn_dbconsole(replica) + assert_output("sqlite>", primary) ensure - master.puts ".exit" + primary.puts ".exit" end def test_respect_environment_option @@ -56,14 +56,14 @@ module ApplicationTests database: db/production.sqlite3 YAML - master, slave = PTY.open - spawn_dbconsole(slave, "-e production") - assert_output("sqlite>", master) + primary, replica = PTY.open + spawn_dbconsole(replica, "-e production") + assert_output("sqlite>", primary) - master.puts "pragma database_list;" - assert_output("production.sqlite3", master) + primary.puts "pragma database_list;" + assert_output("production.sqlite3", primary) ensure - master.puts ".exit" + primary.puts ".exit" end private diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index e408760ecc..a87f453075 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -9,6 +9,7 @@ module ApplicationTests def setup build_app + add_to_env_config("development", "config.active_support.deprecation = :stderr") end def teardown diff --git a/railties/test/application/rake/initializers_test.rb b/railties/test/application/rake/initializers_test.rb index fb498e28ad..8de4967021 100644 --- a/railties/test/application/rake/initializers_test.rb +++ b/railties/test/application/rake/initializers_test.rb @@ -29,6 +29,8 @@ module ApplicationTests end test "`rake initializers` outputs a deprecation warning" do + add_to_env_config("development", "config.active_support.deprecation = :stderr") + stderr = capture(:stderr) { run_rake_initializers } assert_match(/DEPRECATION WARNING: Using `bin\/rake initializers` is deprecated and will be removed in Rails 6.1/, stderr) end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 9e22ba84b5..60802ef7c4 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -10,6 +10,7 @@ module ApplicationTests def setup build_app + add_to_env_config("development", "config.active_support.deprecation = :stderr") require "rails/all" super end diff --git a/railties/test/application/rake/routes_test.rb b/railties/test/application/rake/routes_test.rb index e49ce50b69..2c23ff4679 100644 --- a/railties/test/application/rake/routes_test.rb +++ b/railties/test/application/rake/routes_test.rb @@ -28,7 +28,6 @@ update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:forma end test "`rake routes` outputs a deprecation warning" do - remove_from_env_config("development", ".*config\.active_support\.deprecation.*\n") add_to_env_config("development", "config.active_support.deprecation = :stderr") stderr = capture(:stderr) { run_rake_routes } diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 92b991dd05..ab9e910aed 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -40,17 +40,17 @@ module ApplicationTests f.puts "require 'bundler/setup'" end - master, slave = PTY.open + primary, replica = PTY.open pid = nil begin - pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: slave, out: slave, err: slave) - assert_output("Listening", master) + pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: replica, out: replica, err: replica) + assert_output("Listening", primary) rails("restart") - assert_output("Restarting", master) - assert_output("Inherited", master) + assert_output("Restarting", primary) + assert_output("Inherited", primary) ensure kill(pid) if pid end diff --git a/railties/test/engine/commands_test.rb b/railties/test/engine/commands_test.rb index aeb64d445b..48c93af80c 100644 --- a/railties/test/engine/commands_test.rb +++ b/railties/test/engine/commands_test.rb @@ -33,29 +33,29 @@ class Rails::Engine::CommandsTest < ActiveSupport::TestCase def test_console_command_work_inside_engine skip "PTY unavailable" unless available_pty? - master, slave = PTY.open - spawn_command("console", slave) - assert_output(">", master) + primary, replica = PTY.open + spawn_command("console", replica) + assert_output(">", primary) ensure - master.puts "quit" + primary.puts "quit" end def test_dbconsole_command_work_inside_engine skip "PTY unavailable" unless available_pty? - master, slave = PTY.open - spawn_command("dbconsole", slave) - assert_output("sqlite>", master) + primary, replica = PTY.open + spawn_command("dbconsole", replica) + assert_output("sqlite>", primary) ensure - master.puts ".exit" + primary.puts ".exit" end def test_server_command_work_inside_engine skip "PTY unavailable" unless available_pty? - master, slave = PTY.open - pid = spawn_command("server", slave) - assert_output("Listening on", master) + primary, replica = PTY.open + pid = spawn_command("server", replica) + assert_output("Listening on", primary) ensure kill(pid) end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 28ac3611b7..20acd23329 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -414,9 +414,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_creating_gemspec run_generator - assert_file "bukkits.gemspec", /s\.name\s+= "bukkits"/ - assert_file "bukkits.gemspec", /s\.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/ - assert_file "bukkits.gemspec", /s\.version\s+ = Bukkits::VERSION/ + assert_file "bukkits.gemspec", /spec\.name\s+= "bukkits"/ + assert_file "bukkits.gemspec", /spec\.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/ + assert_file "bukkits.gemspec", /spec\.version\s+ = Bukkits::VERSION/ end def test_usage_of_engine_commands |