aboutsummaryrefslogtreecommitdiffstats
path: root/guides
diff options
context:
space:
mode:
Diffstat (limited to 'guides')
-rw-r--r--guides/CHANGELOG.md19
-rw-r--r--guides/Rakefile11
-rw-r--r--guides/assets/images/getting_started/template_is_missing_articles_new.pngbin472167 -> 26796 bytes
-rw-r--r--guides/assets/stylesheets/main.rtl.css762
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb10
-rw-r--r--guides/bug_report_templates/action_controller_master.rb8
-rw-r--r--guides/bug_report_templates/active_job_gem.rb10
-rw-r--r--guides/bug_report_templates/active_job_master.rb10
-rw-r--r--guides/bug_report_templates/active_record_gem.rb12
-rw-r--r--guides/bug_report_templates/active_record_master.rb7
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb12
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb10
-rw-r--r--guides/bug_report_templates/benchmark.rb7
-rw-r--r--guides/bug_report_templates/generic_gem.rb10
-rw-r--r--guides/bug_report_templates/generic_master.rb7
-rw-r--r--guides/rails_guides.rb13
-rw-r--r--guides/rails_guides/generator.rb35
-rw-r--r--guides/rails_guides/markdown.rb8
-rw-r--r--guides/source/5_1_release_notes.md2
-rw-r--r--guides/source/5_2_release_notes.md4
-rw-r--r--guides/source/6_0_release_notes.md49
-rw-r--r--guides/source/action_cable_overview.md333
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailbox_basics.md398
-rw-r--r--guides/source/action_mailer_basics.md79
-rw-r--r--guides/source/action_text_overview.md99
-rw-r--r--guides/source/active_job_basics.md3
-rw-r--r--guides/source/active_record_basics.md16
-rw-r--r--guides/source/active_record_callbacks.md21
-rw-r--r--guides/source/active_record_migrations.md16
-rw-r--r--guides/source/active_record_postgresql.md2
-rw-r--r--guides/source/active_record_querying.md4
-rw-r--r--guides/source/active_record_validations.md5
-rw-r--r--guides/source/active_storage_overview.md33
-rw-r--r--guides/source/active_support_core_extensions.md89
-rw-r--r--guides/source/active_support_instrumentation.md62
-rw-r--r--guides/source/api_app.md2
-rw-r--r--guides/source/asset_pipeline.md68
-rw-r--r--guides/source/association_basics.md10
-rw-r--r--guides/source/caching_with_rails.md12
-rw-r--r--guides/source/command_line.md50
-rw-r--r--guides/source/configuring.md117
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md23
-rw-r--r--guides/source/debugging_rails_applications.md2
-rw-r--r--guides/source/development_dependencies_install.md363
-rw-r--r--guides/source/documents.yaml34
-rw-r--r--guides/source/engines.md29
-rw-r--r--guides/source/generators.md6
-rw-r--r--guides/source/getting_started.md27
-rw-r--r--guides/source/i18n.md68
-rw-r--r--guides/source/index.html.erb5
-rw-r--r--guides/source/layout.html.erb9
-rw-r--r--guides/source/rails_application_templates.md6
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/routing.md6
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md4
-rw-r--r--guides/source/security.md104
-rw-r--r--guides/source/testing.md244
-rw-r--r--guides/source/upgrading_ruby_on_rails.md40
59 files changed, 2393 insertions, 1006 deletions
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 516b643cb8..41037146a0 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,10 +1,25 @@
+## Rails 6.0.0.beta2 (February 25, 2019) ##
+
+* No changes.
+
+
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Add "Action Text Overview" Guide.
+
+ *DHH*
+
+* Add "Action Mailbox Basics" Guide.
+
+ *DHH*
+
* New section _Troubleshooting_ in the _Autoloading and Reloading Constants_ guide.
*Xavier Noria*
-* Rails 6 requires Ruby 2.4.1 or newer.
+* Rails 6 requires Ruby 2.5.0 or newer.
- *Jeremy Daer*
+ *Jeremy Daer*, *Kasper Timm Hansen*
Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/Rakefile b/guides/Rakefile
index 4116e6f9cc..b7425f6de4 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -88,4 +88,15 @@ HELP
end
end
+task :test do
+ templates = Dir.glob("bug_report_templates/*.rb")
+ counter = templates.count do |file|
+ puts "--- Running #{file}"
+ Bundler.clean_system(Gem.ruby, "-w", file) ||
+ puts("+++ 💥 FAILED (exit #{$?.exitstatus})")
+ end
+ puts "+++ #{counter} / #{templates.size} templates executed successfully"
+ exit 1 if counter < templates.size
+end
+
task default: "guides:help"
diff --git a/guides/assets/images/getting_started/template_is_missing_articles_new.png b/guides/assets/images/getting_started/template_is_missing_articles_new.png
index a1603f5d28..830b19bd9d 100644
--- a/guides/assets/images/getting_started/template_is_missing_articles_new.png
+++ b/guides/assets/images/getting_started/template_is_missing_articles_new.png
Binary files differ
diff --git a/guides/assets/stylesheets/main.rtl.css b/guides/assets/stylesheets/main.rtl.css
new file mode 100644
index 0000000000..ea31d6017c
--- /dev/null
+++ b/guides/assets/stylesheets/main.rtl.css
@@ -0,0 +1,762 @@
+/* Guides.rubyonrails.org */
+/* Main.css */
+/* Created January 30, 2009 */
+/* Modified February 8, 2009
+--------------------------------------- */
+
+/* General
+--------------------------------------- */
+
+.right {float: right; margin-left: 1em;}
+.left {float: left; margin-right: 1em;}
+@media screen and (max-width: 480px) {
+ .right, .left { float: none; }
+}
+.small {font-size: smaller;}
+.large {font-size: larger;}
+.hide {display: none;}
+
+ul, ol { margin: 0 1.5em 1.5em 1.5em; }
+
+ul { list-style-type: disc; }
+ol { list-style-type: decimal; }
+
+dl { margin: 0 0 1.5em 0; }
+dl dt { font-weight: bold; }
+dd { margin-right: 1.5em;}
+
+pre, code {
+ font-size: 1em;
+ font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+ line-height: 1.5;
+ margin: 1.5em 0;
+ overflow: auto;
+ color: #222;
+}
+
+p code {
+ background: #eee;
+ border-radius: 2px;
+ padding: 1px 3px;
+}
+
+pre, tt, code {
+ white-space: pre-wrap; /* css-3 */
+ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+}
+
+abbr, acronym { border-bottom: 1px dotted #666; }
+address { margin: 0 0 1.5em; font-style: italic; }
+del { color:#666; }
+
+blockquote { margin: 1.5em; color: #666; font-style: italic; }
+strong { font-weight: bold; }
+em, dfn { font-style: italic; }
+dfn { font-weight: bold; }
+sup, sub { line-height: 0; }
+p {margin: 0 0 1.5em;}
+
+label { font-weight: bold; }
+fieldset { padding:1.4em; margin: 0 0 1.5em 0; border: 1px solid #ccc; }
+legend { font-weight: bold; font-size:1.2em; }
+
+input.text, input.title,
+textarea, select {
+ margin:0.5em 0;
+ border:1px solid #bbb;
+}
+
+table {
+ margin: 0 0 1.5em;
+ border: 2px solid #CCC;
+ background: #FFF;
+ border-collapse: collapse;
+}
+
+table th, table td {
+ padding: 9px 10px;
+ border: 1px solid #CCC;
+ border-collapse: collapse;
+}
+
+table th {
+ border-bottom: 2px solid #CCC;
+ background: #EEE;
+ font-weight: bold;
+}
+
+img {
+ max-width: 100%;
+}
+
+
+/* Structure and Layout
+--------------------------------------- */
+
+body {
+ text-align: center;
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 87.5%;
+ line-height: 1.5em;
+ background: #fff;
+ color: #999;
+ direction: rtl;
+}
+
+.wrapper {
+ text-align: right;
+ margin: 0 auto;
+ max-width: 960px;
+ padding: 0 1em;
+}
+
+.red-button {
+ display: inline-block;
+ border-top: 1px solid rgba(255,255,255,.5);
+ background: #751913;
+ background: -webkit-gradient(linear, right top, right bottom, from(#c52f24), to(#751913));
+ background: -webkit-linear-gradient(top, #c52f24, #751913);
+ background: -moz-linear-gradient(top, #c52f24, #751913);
+ background: -ms-linear-gradient(top, #c52f24, #751913);
+ background: -o-linear-gradient(top, #c52f24, #751913);
+ padding: 9px 18px;
+ -webkit-border-radius: 11px;
+ -moz-border-radius: 11px;
+ border-radius: 11px;
+ -webkit-box-shadow: rgba(0,0,0,1) 0 1px 0;
+ -moz-box-shadow: rgba(0,0,0,1) 0 1px 0;
+ box-shadow: rgba(0,0,0,1) 0 1px 0;
+ text-shadow: rgba(0,0,0,.4) 0 1px 0;
+ color: white;
+ font-size: 15px;
+ font-family: Helvetica, Arial, Sans-Serif;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: pointer;
+}
+.red-button:active {
+ border-top: none;
+ padding-top: 10px;
+ background: -webkit-gradient(linear, right top, right bottom, from(#751913), to(#c52f24));
+ background: -webkit-linear-gradient(top, #751913, #c52f24);
+ background: -moz-linear-gradient(top, #751913, #c52f24);
+ background: -ms-linear-gradient(top, #751913, #c52f24);
+ background: -o-linear-gradient(top, #751913, #c52f24);
+}
+
+#topNav {
+ padding: 1em 0;
+ color: #565656;
+ background: #222;
+}
+
+.s-hidden {
+ display: none;
+}
+
+@media screen and (min-width: 1025px) {
+ .more-info-button {
+ display: none;
+ }
+ .more-info-links {
+ list-style: none;
+ display: inline;
+ margin: 0;
+ }
+
+ .more-info {
+ display: inline-block;
+ }
+ .more-info:after {
+ content: " |";
+ }
+
+ .more-info:last-child:after {
+ content: "";
+ }
+}
+
+@media screen and (max-width: 1024px) {
+ #topNav .wrapper { text-align: center; }
+ .more-info-button {
+ position: relative;
+ z-index: 25;
+ }
+
+ .more-info-label {
+ display: none;
+ }
+
+ .more-info-container {
+ position: absolute;
+ top: .5em;
+ z-index: 20;
+ margin: 0 auto;
+ right: 0;
+ left: 0;
+ width: 20em;
+ }
+
+ .more-info-links {
+ display: block;
+ list-style: none;
+ background-color: #c52f24;
+ border-radius: 5px;
+ padding-top: 5.25em;
+ border: 1px #980905 solid;
+ }
+ .more-info-links.s-hidden {
+ display: none;
+ }
+ .more-info {
+ padding: .75em;
+ border-top: 1px #980905 solid;
+ }
+ .more-info a, .more-info a:link, .more-info a:visited {
+ display: block;
+ color: white;
+ width: 100%;
+ height: 100%;
+ text-decoration: none;
+ text-transform: uppercase;
+ }
+}
+
+#header {
+ background: #c52f24 url(../images/header_tile.gif) repeat-x;
+ color: #FFF;
+ padding: 1.5em 0;
+ z-index: 99;
+}
+
+#feature {
+ background: #d5e9f6 url(../images/feature_tile.gif) repeat-x;
+ color: #333;
+ padding: 0.5em 0 1.5em;
+}
+
+#container {
+ color: #333;
+ padding: 0.5em 0 1.5em 0;
+}
+
+#mainCol {
+ max-width: 630px;
+ margin-right: 2em;
+}
+
+#subCol {
+ position: absolute;
+ z-index: 0;
+ top: 21px;
+ left: 0;
+ background: #FFF;
+ padding: 1em 1.5em 1em 1.25em;
+ width: 17em;
+ font-size: 0.9285em;
+ line-height: 1.3846em;
+ margin-left: 1em;
+}
+
+
+@media screen and (max-width: 800px) {
+ #subCol {
+ position: static;
+ width: inherit;
+ margin-right: -1em;
+ margin-left: 0;
+ padding-left: 1.25em;
+ }
+}
+
+#footer {
+ padding: 2em 0;
+ background: #222 url(../images/footer_tile.gif) repeat-x;
+}
+#footer .wrapper {
+ padding-right: 1em;
+ max-width: 960px;
+}
+
+#header .wrapper, #topNav .wrapper, #feature .wrapper {padding-right: 1em; max-width: 960px;}
+#feature .wrapper {max-width: 640px; padding-left: 23em; position: relative; z-index: 0;}
+
+@media screen and (max-width: 960px) {
+ #container .wrapper { padding-left: 23em; }
+}
+
+@media screen and (max-width: 800px) {
+ #feature .wrapper, #container .wrapper { padding-left: 0; }
+}
+
+/* Links
+--------------------------------------- */
+
+a, a:link, a:visited {
+ color: #ee3f3f;
+ text-decoration: underline;
+}
+
+#mainCol a, #subCol a, #feature a {color: #980905;}
+#mainCol a code, #subCol a code, #feature a code {color: #980905;}
+
+#mainCol a.anchorlink, #mainCol a.anchorlink code {color: #333;}
+#mainCol a.anchorlink { text-decoration: none; }
+#mainCol a.anchorlink:hover { text-decoration: underline; }
+
+/* Navigation
+--------------------------------------- */
+
+.nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ float: left;
+ margin-top: 1.5em;
+ font-size: 1.2857em;
+}
+
+.nav .nav-item {color: #FFF; text-decoration: none;}
+.nav .nav-item:hover {text-decoration: underline;}
+
+.guides-index-large, .guides-index-small .guides-index-item {
+ padding: 0.5em 1.5em;
+ border-radius: 1em;
+ -webkit-border-radius: 1em;
+ -moz-border-radius: 1em;
+ background: #980905;
+ position: relative;
+ color: white;
+}
+
+.guides-index .guides-index-item {
+ background: #980905 url(../images/nav_arrow.gif) no-repeat left top;
+ padding-left: 1em;
+ position: relative;
+ z-index: 15;
+ padding-bottom: 0.125em;
+}
+
+.guides-index:hover .guides-index-item, .guides-index .guides-index-item:hover {
+ background-position: left -81px;
+ text-decoration: underline !important;
+}
+
+@media screen and (min-width: 481px) {
+ .nav {
+ float: left;
+ margin-top: 1.5em;
+ font-size: 1.2857em;
+ }
+ .nav>li {
+ display: inline;
+ margin-right: 0.5em;
+ }
+ .guides-index.guides-index-small {
+ display: none;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .nav {
+ float: none;
+ width: 100%;
+ text-align: center;
+ }
+ .nav .nav-item {
+ display: block;
+ margin: 0;
+ width: 100%;
+ background-color: #980905;
+ border: solid 1px #620c04;
+ border-top: 0;
+ padding: 15px 0;
+ text-align: center;
+ }
+ .nav .nav-item, .nav-item.guides-index-item {
+ text-transform: uppercase;
+ }
+ .nav .nav-item:first-child, .nav-item.guides-index-small {
+ border-top: solid 1px #620c04;
+ }
+ .guides-index.guides-index-small {
+ display: block;
+ margin-top: 1.5em;
+ }
+ .guides-index.guides-index-large {
+ display: none;
+ }
+ .guides-index-small .guides-index-item {
+ font: inherit;
+ padding-right: .75em;
+ font-size: .95em;
+ background-position: 96% 16px;
+ -webkit-appearance: none;
+ }
+ .guides-index-small .guides-index-item:hover{
+ background-position: 96% -65px;
+ }
+}
+
+#guides {
+ width: 37em;
+ display: block;
+ background: #980905;
+ border-radius: 1em;
+ color: #f1938c;
+ padding: 1.5em 2em;
+ position: absolute;
+ z-index: 10;
+ top: -0.25em;
+ left: 0;
+ padding-top: 2em;
+}
+
+#guides.visible {
+ display: block !important;
+}
+
+.guides-section dt, .guides-section dd {
+ font-weight: normal;
+ font-size: 0.722em;
+ margin: 0;
+ padding: 0;
+}
+.guides-section dt {
+ margin: 0.5em 0 0;
+ padding:0;
+}
+#guides a {
+ background: none !important;
+ color: #FFF;
+ text-decoration: none;
+}
+#guides a:hover {
+ text-decoration: underline;
+}
+.guides-section-container {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+ width: 100%;
+ max-height: 35em;
+}
+
+.guides-section {
+ min-width: 5em;
+ margin: 0 2em 0.5em 0;
+ flex: auto;
+ max-width: 12em;
+}
+
+.guides-section dd {
+ line-height: 1.3;
+ margin-bottom: 0.5em;
+}
+
+#guides hr {
+ display: block;
+ border: none;
+ height: 1px;
+ color: #f1938c;
+ background: #f1938c;
+}
+
+/* Headings
+--------------------------------------- */
+
+h1 {
+ font-size: 2.5em;
+ line-height: 1em;
+ margin: 0.6em 0 .2em;
+ font-weight: bold;
+}
+
+h2 {
+ font-size: 2.1428em;
+ line-height: 1em;
+ margin: 0.7em 0 .2333em;
+ font-weight: bold;
+}
+
+@media screen and (max-width: 480px) {
+ h2 {
+ font-size: 1.45em;
+ }
+}
+
+h3 {
+ font-size: 1.7142em;
+ line-height: 1.286em;
+ margin: 0.875em 0 0.2916em;
+ font-weight: bold;
+}
+
+@media screen and (max-width: 480px) {
+ h3 {
+ font-size: 1.45em;
+ }
+}
+
+h4 {
+ font-size: 1.2857em;
+ line-height: 1.2em;
+ margin: 1.6667em 0 .3887em;
+ font-weight: bold;
+}
+
+h5 {
+ font-size: 1em;
+ line-height: 1.5em;
+ margin: 1em 0 .5em;
+ font-weight: bold;
+}
+
+h6 {
+ font-size: 1em;
+ line-height: 1.5em;
+ margin: 1em 0 .5em;
+ font-weight: normal;
+}
+
+.section {
+ padding-bottom: 0.25em;
+ border-bottom: 1px solid #999;
+}
+
+/* Content
+--------------------------------------- */
+
+.pic {
+ margin: 0 2em 2em 0;
+}
+
+#topNav strong {color: #999; margin-left: 0.5em;}
+#topNav strong a {color: #FFF;}
+
+#header h1 {
+ float: right;
+ background: url(../images/rails_guides_logo_1x.png) no-repeat;
+ width: 297px;
+ text-indent: -9999em;
+ margin: 0;
+ padding: 0;
+}
+
+@media
+only screen and (-webkit-min-device-pixel-ratio: 2),
+only screen and ( min--moz-device-pixel-ratio: 2),
+only screen and ( -o-min-device-pixel-ratio: 2/1),
+only screen and ( min-device-pixel-ratio: 2),
+only screen and ( min-resolution: 192dpi),
+only screen and ( min-resolution: 2dppx) {
+ #header h1 {
+ background: url(../images/rails_guides_logo_2x.png) no-repeat;
+ background-size: 160%;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ #header h1 {
+ float: none;
+ }
+}
+
+#header h1 a {
+ text-decoration: none;
+ display: block;
+ height: 77px;
+}
+
+#feature p {
+ font-size: 1.2857em;
+ margin-bottom: 0.75em;
+}
+
+@media screen and (max-width: 480px) {
+ #feature p {
+ font-size: 1em;
+ }
+}
+
+#feature ul {margin-right: 0;}
+#feature ul li {
+ list-style: none;
+ background: url(../images/check_bullet.gif) no-repeat right 0.5em;
+ padding: 0.5em 1.75em 0.5em 1.75em;
+ font-size: 1.1428em;
+ font-weight: bold;
+}
+
+#mainCol dd, #subCol dd {
+ padding: 0.25em 0 1em;
+ border-bottom: 1px solid #CCC;
+ margin-bottom: 1em;
+ margin-right: 0;
+ /*padding-right: 28px;*/
+ padding-right: 0;
+}
+
+#mainCol dt, #subCol dt {
+ font-size: 1.2857em;
+ padding: 0.125em 0 0.25em 0;
+ margin-bottom: 0;
+}
+
+@media screen and (max-width: 480px) {
+ #mainCol dt, #subCol dt {
+ font-size: 1em;
+ }
+}
+
+#mainCol dd.work-in-progress, #subCol dd.work-in-progress {
+ background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-right: 0;
+ margin-top: 0.25em;
+}
+
+#mainCol dd.kindle, #subCol dd.kindle {
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-right: 0;
+ margin-top: 0.25em;
+}
+
+#mainCol div.warning, #subCol dd.warning {
+ background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1.25em 0.25em 48px;
+ margin-right: 0;
+ margin-top: 0.25em;
+}
+
+#subCol .chapters {color: #980905;}
+#subCol .chapters a {font-weight: bold;}
+#subCol .chapters ul a {font-weight: normal;}
+#subCol .chapters li {margin-bottom: 0.75em;}
+#subCol h3.chapter {margin-top: 0.25em;}
+#subCol h3.chapter img {vertical-align: text-bottom;}
+#subCol .chapters ul {margin-right: 0; margin-top: 0.5em;}
+#subCol .chapters ul li {
+ list-style: none;
+ padding: 0 1em 0 0;
+ background: url(../images/bullet.gif) no-repeat right 0.45em;
+ margin-right: 0;
+ font-size: 1em;
+ font-weight: normal;
+}
+
+#subCol li ul, li ol { margin:0 1.5em; }
+
+div.code_container {
+ background: #EEE url(../images/tab_grey.gif) no-repeat right top;
+ padding: 0.25em 48px 0.5em 1em;
+}
+
+.note {
+ background: #fff9d8 url(../images/tab_note.gif) no-repeat right top;
+ border: none;
+ padding: 1em 48px 0.25em 1em;
+ margin: 0.25em 0 1.5em 0;
+}
+
+.info {
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat right top;
+ border: none;
+ padding: 1em 48px 0.25em 1em;
+ margin: 0.25em 0 1.5em 0;
+}
+
+#mainCol div.todo {
+ background: #fff9d8 url(../images/tab_yellow.gif) no-repeat right top;
+ border: none;
+ padding: 1em 48px 0.25em 1em;
+ margin: 0.25em 0 1.5em 0;
+}
+
+.note code, .info code, .todo code {
+ background: #fff;
+}
+
+#mainCol ul li {
+ list-style:none;
+ background: url(../images/grey_bullet.gif) no-repeat right 0.5em;
+ padding-right: 1em;
+ margin-right: 0;
+}
+
+#subCol .content {
+ font-size: 0.7857em;
+ line-height: 1.5em;
+}
+
+#subCol .content li {
+ font-weight: normal;
+ background: none;
+ padding: 0 0 1em;
+ font-size: 1.1667em;
+}
+
+/* Clearing
+--------------------------------------- */
+
+.clearfix:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+* html .clearfix {height: 1%;}
+.clearfix {display: block;}
+
+/* Same bottom margin for special boxes than for regular paragraphs, this way
+intermediate whitespace looks uniform. */
+div.code_container, div.important, div.caution, div.warning, div.note, div.info {
+ margin-bottom: 1.5em;
+}
+
+/* Remove bottom margin of paragraphs in special boxes, otherwise they get a
+spurious blank area below with the box background. */
+div.important p, div.caution p, div.warning p, div.note p, div.info p {
+ margin-bottom: 1em;
+}
+
+/* Edge Badge
+--------------------------------------- */
+
+#edge-badge {
+ position: fixed;
+ right: 0px;
+ top: 0px;
+ z-index: 100;
+ border: none;
+}
+
+/* Foundation v2.1.4 http://foundation.zurb.com */
+/* Artfully masterminded by ZURB */
+
+/* Mobile */
+@media only screen and (max-width: 767px) {
+ table.responsive { margin-bottom: 0; }
+
+ .pinned { position: absolute; right: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-left: 1px solid #ccc; border-right: 1px solid #ccc; }
+ .pinned table { border-left: none; border-right: none; width: 100%; }
+ .pinned table th, .pinned table td { white-space: nowrap; }
+ .pinned td:last-child { border-bottom: 0; }
+
+ div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-left: 1px solid #ccc; }
+ div.table-wrapper div.scrollable table { margin-right: 35%; }
+ div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; }
+
+ table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; }
+ table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; }
+
+}
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index f339635fb7..6c74200761 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -42,9 +37,6 @@ end
require "minitest/autorun"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BugTest < Minitest::Test
include Rack::Test::Methods
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index ffd81c0079..682269163a 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -19,6 +14,7 @@ require "action_controller/railtie"
class TestApp < Rails::Application
config.root = __dir__
+ config.hosts << "example.org"
secrets.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index b260f0835b..eb9d1316e9 100644
--- a/guides/bug_report_templates/active_job_gem.rb
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -19,9 +14,6 @@ end
require "minitest/autorun"
require "active_job"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BuggyJob < ActiveJob::Base
def perform
puts "performed"
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index 894581da96..ae3ef7752f 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -18,9 +13,6 @@ end
require "active_job"
require "minitest/autorun"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BuggyJob < ActiveJob::Base
def perform
puts "performed"
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 5f70dbbe69..74b329115d 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -14,16 +9,13 @@ gemfile(true) do
# Activate the gem you are reporting the issue against.
gem "activerecord", "5.2.0"
- gem "sqlite3"
+ gem "sqlite3", "~> 1.3.6"
end
require "active_record"
require "minitest/autorun"
require "logger"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 914f04f51a..780456b7b6 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
index 7f7359fa78..8d71863034 100644
--- a/guides/bug_report_templates/active_record_migrations_gem.rb
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -14,16 +9,13 @@ gemfile(true) do
# Activate the gem you are reporting the issue against.
gem "activerecord", "5.2.0"
- gem "sqlite3"
+ gem "sqlite3", "~> 1.3.6"
end
require "active_record"
require "minitest/autorun"
require "logger"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 106d94491c..b0fe3bc660 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -20,9 +15,6 @@ require "active_record"
require "minitest/autorun"
require "logger"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb
index 046572148b..4a8ce787c7 100644
--- a/guides/bug_report_templates/benchmark.rb
+++ b/guides/bug_report_templates/benchmark.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index aec5bf0577..3fd54437f7 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -20,9 +15,6 @@ require "active_support"
require "active_support/core_ext/object/blank"
require "minitest/autorun"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BugTest < Minitest::Test
def test_stuff
assert "zomg".present?
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index 727f428960..ec65fee292 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index f2d4d6f647..a72acdbd06 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -20,10 +20,11 @@ version = env_value["RAILS_VERSION"]
edge = `git rev-parse HEAD`.strip unless version
RailsGuides::Generator.new(
- edge: edge,
- version: version,
- all: env_flag["ALL"],
- only: env_value["ONLY"],
- kindle: env_flag["KINDLE"],
- language: env_value["GUIDES_LANGUAGE"]
+ edge: edge,
+ version: version,
+ all: env_flag["ALL"],
+ only: env_value["ONLY"],
+ kindle: env_flag["KINDLE"],
+ language: env_value["GUIDES_LANGUAGE"],
+ direction: env_value["DIRECTION"]
).generate
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index c83538ad48..b0ddb0e7e0 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -17,13 +17,14 @@ module RailsGuides
class Generator
GUIDES_RE = /\.(?:erb|md)\z/
- def initialize(edge:, version:, all:, only:, kindle:, language:)
- @edge = edge
- @version = version
- @all = all
- @only = only
- @kindle = kindle
- @language = language
+ def initialize(edge:, version:, all:, only:, kindle:, language:, direction: "ltr")
+ @edge = edge
+ @version = version
+ @all = all
+ @only = only
+ @kindle = kindle
+ @language = language
+ @direction = direction
if @kindle
check_for_kindlegen
@@ -62,9 +63,9 @@ module RailsGuides
end
def mobi
- mobi = "ruby_on_rails_guides_#{@version || @edge[0, 7]}"
- mobi += ".#{@language}" if @language
- mobi += ".mobi"
+ mobi = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
+ mobi << ".#{@language}" if @language
+ mobi << ".mobi"
end
def initialize_dirs
@@ -116,6 +117,14 @@ module RailsGuides
def copy_assets
FileUtils.cp_r(Dir.glob("#{@guides_dir}/assets/*"), @output_dir)
+
+ if @direction == "rtl"
+ overwrite_css_with_right_to_left_direction
+ end
+ end
+
+ def overwrite_css_with_right_to_left_direction
+ FileUtils.mv("#{@output_dir}/stylesheets/main.rtl.css", "#{@output_dir}/stylesheets/main.css")
end
def output_file_for(guide)
@@ -141,8 +150,8 @@ module RailsGuides
puts "Generating #{guide} as #{output_file}"
layout = @kindle ? "kindle/layout" : "layout"
- view = ActionView::Base.new(
- @source_dir,
+ view = ActionView::Base.with_view_paths(
+ [@source_dir],
edge: @edge,
version: @version,
mobi: "kindle/#{mobi}",
@@ -198,7 +207,7 @@ module RailsGuides
def check_fragment_identifiers(html, anchors)
html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier|
next if fragment_identifier == "mainCol" # in layout, jumps to some DIV
- unless anchors.member?(fragment_identifier)
+ unless anchors.member?(CGI.unescape(fragment_identifier))
guess = anchors.min { |a, b|
Levenshtein.distance(fragment_identifier, a) <=> Levenshtein.distance(fragment_identifier, b)
}
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index a98aa8fe66..018f49ffd0 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -3,6 +3,7 @@
require "redcarpet"
require "nokogiri"
require "rails_guides/markdown/renderer"
+require "rails-html-sanitizer"
module RailsGuides
class Markdown
@@ -20,6 +21,7 @@ module RailsGuides
@raw_body = body
extract_raw_header_and_body
generate_header
+ generate_description
generate_title
generate_body
generate_structure
@@ -82,6 +84,11 @@ module RailsGuides
@header = engine.render(@raw_header).html_safe
end
+ def generate_description
+ sanitizer = Rails::Html::FullSanitizer.new
+ @description = sanitizer.sanitize(@header).squish
+ end
+
def generate_structure
@headings_for_index = []
if @body.present?
@@ -165,6 +172,7 @@ module RailsGuides
def render_page
@view.content_for(:header_section) { @header }
+ @view.content_for(:description) { @description }
@view.content_for(:page_title) { @title }
@view.content_for(:index_section) { @index }
@view.render(layout: @layout, html: @body.html_safe)
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index d26d3d3b95..a5a7eb4b2e 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -399,7 +399,7 @@ Please refer to the [Changelog][action-view] for detailed changes.
* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local`
fields.
- ([Pull Request](https://github.com/rails/rails/pull/28061))
+ ([Pull Request](https://github.com/rails/rails/pull/25469))
* New Builder-style syntax for HTML tags (`tag.div`, `tag.br`, etc.)
([Pull Request](https://github.com/rails/rails/pull/25543))
diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md
index c5b914fffc..29b355119c 100644
--- a/guides/source/5_2_release_notes.md
+++ b/guides/source/5_2_release_notes.md
@@ -615,6 +615,10 @@ Please refer to the [Changelog][active-record] for detailed changes.
the parent class was getting deleted when the child was not.
([Commit](https://github.com/rails/rails/commit/b0fc04aa3af338d5a90608bf37248668d59fc881))
+* Idle database connections (previously just orphaned connections) are now
+ periodically reaped by the connection pool reaper.
+ ([Commit](https://github.com/rails/rails/pull/31221/commits/9027fafff6da932e6e64ddb828665f4b01fc8902))
+
Active Model
------------
diff --git a/guides/source/6_0_release_notes.md b/guides/source/6_0_release_notes.md
index f3ed21dc45..e6fb15c09c 100644
--- a/guides/source/6_0_release_notes.md
+++ b/guides/source/6_0_release_notes.md
@@ -5,7 +5,10 @@ Ruby on Rails 6.0 Release Notes
Highlights in Rails 6.0:
+* Action Mailbox
+* Action Text
* Parallel Testing
+* Action Cable Testing
These release notes cover only the major changes. To learn about various bug
fixes and changes, please refer to the change logs or check out the [list of
@@ -28,6 +31,29 @@ guide.
Major Features
--------------
+### Action Mailbox
+
+[Pull Request](https://github.com/rails/rails/pull/34786)
+
+[Action Mailbox](https://github.com/rails/rails/tree/6-0-stable/actionmailbox) allows you
+to route incoming emails to controller-like mailboxes.
+You can read more about Action Mailbox in the [Action Mailbox Basics](action_mailbox_basics.html) guide.
+
+### Action Text
+
+[Pull Request](https://github.com/rails/rails/pull/34873)
+
+[Action Text](https://github.com/rails/rails/tree/6-0-stable/actiontext)
+brings rich text content and editing to Rails. It includes
+the [Trix editor](https://trix-editor.org) that handles everything from formatting
+to links to quotes to lists to embedded images and galleries.
+The rich text content generated by the Trix editor is saved in its own
+RichText model that's associated with any existing Active Record model in the application.
+Any embedded images (or other attachments) are automatically stored using
+Active Storage and associated with the included RichText model.
+
+You can read more about Action Text in the [Action Text Overview](action_text_overview.html) guide.
+
### Parallel Testing
[Pull Request](https://github.com/rails/rails/pull/31900)
@@ -37,6 +63,13 @@ test suite. While forking processes is the default method, threading is
supported as well. Running tests in parallel reduces the time it takes
your entire test suite to run.
+### Action Cable Testing
+
+[Pull Request](https://github.com/rails/rails/pull/33659)
+
+[Action Cable testing tools](testing.html#testing-action-cable) allow you to test your
+Action Cable functionality at any level: connections, channels, broadcasts.
+
Railties
--------
@@ -59,6 +92,22 @@ Please refer to the [Changelog][action-cable] for detailed changes.
### Notable changes
+* The ActionCable javascript package has been converted from CoffeeScript
+ to ES2015, and we now publish the source code in the npm distribution.
+
+ This allows ActionCable users to depend on the javascript source code
+ rather than the compiled code, which can produce smaller javascript bundles.
+
+ This change includes some breaking changes to optional parts of the
+ ActionCable javascript API:
+
+ - Configuration of the WebSocket adapter and logger adapter have been moved
+ from properties of `ActionCable` to properties of `ActionCable.adapters`.
+
+ - The `ActionCable.startDebugging()` and `ActionCable.stopDebugging()`
+ methods have been removed and replaced with the property
+ `ActionCable.logger.enabled`.
+
Action Pack
-----------
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index e6c0ae31a8..7a9ff96c44 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -27,6 +27,36 @@ client-side JavaScript framework and a server-side Ruby framework. You have
access to your full domain model written with Active Record or your ORM of
choice.
+Terminology
+-----------
+
+A single Action Cable server can handle multiple connection instances. It has one
+connection instance per WebSocket connection. A single user may have multiple
+WebSockets open to your application if they use multiple browser tabs or devices.
+The client of a WebSocket connection is called the consumer.
+
+Each consumer can in turn subscribe to multiple cable channels. Each channel
+encapsulates a logical unit of work, similar to what a controller does in
+a regular MVC setup. For example, you could have a `ChatChannel` and
+an `AppearancesChannel`, and a consumer could be subscribed to either
+or to both of these channels. At the very least, a consumer should be subscribed
+to one channel.
+
+When the consumer is subscribed to a channel, they act as a subscriber.
+The connection between the subscriber and the channel is, surprise-surprise,
+called a subscription. A consumer can act as a subscriber to a given channel
+any number of times. For example, a consumer could subscribe to multiple chat rooms
+at the same time. (And remember that a physical user may have multiple consumers,
+one per tab/device open to your connection).
+
+Each channel can then again be streaming zero or more broadcastings.
+A broadcasting is a pubsub link where anything transmitted by the broadcaster is
+sent directly to the channel subscribers who are streaming that named broadcasting.
+
+As you can see, this is a fairly deep architectural stack. There's a lot of new
+terminology to identify the new pieces, and on top of that, you're dealing
+with both client and server side reflections of each unit.
+
What is Pub/Sub
---------------
@@ -147,16 +177,13 @@ established using the following JavaScript, which is generated by default by Rai
#### Connect Consumer
```js
-// app/assets/javascripts/cable.js
-//= require action_cable
-//= require_self
-//= require_tree ./channels
+// app/javascript/channels/consumer.js
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the `rails generate channel` command.
-(function() {
- this.App || (this.App = {});
+import { createConsumer } from "@rails/actioncable"
- App.cable = ActionCable.createConsumer();
-}).call(this);
+export default createConsumer()
```
This will ready a consumer that'll connect against `/cable` on your server by default.
@@ -167,12 +194,16 @@ you're interested in having.
A consumer becomes a subscriber by creating a subscription to a given channel:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
-# app/assets/javascripts/cable/subscriptions/appearance.coffee
-App.cable.subscriptions.create { channel: "AppearanceChannel" }
+// app/javascript/channels/appearance_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "AppearanceChannel" })
```
While this creates the subscription, the functionality needed to respond to
@@ -181,9 +212,12 @@ received data will be described later on.
A consumer can act as a subscriber to a given channel any number of times. For
example, a consumer could subscribe to multiple chat rooms at the same time:
-```coffeescript
-App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
-App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
+consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
```
## Client-Server Interactions
@@ -242,9 +276,9 @@ WebNotificationsChannel.broadcast_to(
```
The `WebNotificationsChannel.broadcast_to` call places a message in the current
-subscription adapter (by default `redis` for production and `async` for development and
-test environments)'s pubsub queue under a separate broadcasting name for each user.
-For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.
+subscription adapter's pubsub queue under a separate broadcasting name for each user.
+The default pubsub queue for Action Cable is `redis` in production and `async` in development and
+test environments. For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.
The channel has been instructed to stream everything that arrives at
`web_notifications:1` directly to the client by invoking the `received`
@@ -256,24 +290,31 @@ When a consumer is subscribed to a channel, they act as a subscriber. This
connection is called a subscription. Incoming messages are then routed to
these channel subscriptions based on an identifier sent by the cable consumer.
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-# Assumes you've already requested the right to send web notifications
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- @appendLine(data)
-
- appendLine: (data) ->
- html = @createLine(data)
- $("[data-chat-room='Best Room']").append(html)
-
- createLine: (data) ->
- """
- <article class="chat-line">
- <span class="speaker">#{data["sent_by"]}</span>
- <span class="body">#{data["body"]}</span>
- </article>
- """
+```js
+// app/javascript/channels/chat_channel.js
+// Assumes you've already requested the right to send web notifications
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
+ received(data) {
+ this.appendLine(data)
+ },
+
+ appendLine(data) {
+ const html = this.createLine(data)
+ const element = document.querySelector("[data-chat-room='Best Room']")
+ element.insertAdjacentHTML("beforeend", html)
+ },
+
+ createLine(data) {
+ return `
+ <article class="chat-line">
+ <span class="speaker">${data["sent_by"]}</span>
+ <span class="body">${data["body"]}</span>
+ </article>
+ `
+ }
+})
```
### Passing Parameters to Channels
@@ -293,23 +334,30 @@ end
An object passed as the first argument to `subscriptions.create` becomes the
params hash in the cable channel. The keyword `channel` is required:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- @appendLine(data)
-
- appendLine: (data) ->
- html = @createLine(data)
- $("[data-chat-room='Best Room']").append(html)
-
- createLine: (data) ->
- """
- <article class="chat-line">
- <span class="speaker">#{data["sent_by"]}</span>
- <span class="body">#{data["body"]}</span>
- </article>
- """
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
+ received(data) {
+ this.appendLine(data)
+ },
+
+ appendLine(data) {
+ const html = this.createLine(data)
+ const element = document.querySelector("[data-chat-room='Best Room']")
+ element.insertAdjacentHTML("beforeend", html)
+ },
+
+ createLine(data) {
+ return `
+ <article class="chat-line">
+ <span class="speaker">${data["sent_by"]}</span>
+ <span class="body">${data["body"]}</span>
+ </article>
+ `
+ }
+})
```
```ruby
@@ -340,13 +388,17 @@ class ChatChannel < ApplicationCable::Channel
end
```
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- # data => { sent_by: "Paul", body: "This is a cool chat app." }
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
+ received(data) {
+ // data => { sent_by: "Paul", body: "This is a cool chat app." }
+ }
+}
-App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
+chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
```
The rebroadcast will be received by all connected clients, _including_ the
@@ -396,46 +448,69 @@ appear/disappear API could be backed by Redis, a database, or whatever else.
Create the client-side appearance channel subscription:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/appearance.coffee
-App.cable.subscriptions.create "AppearanceChannel",
- # Called when the subscription is ready for use on the server.
- connected: ->
- @install()
- @appear()
-
- # Called when the WebSocket connection is closed.
- disconnected: ->
- @uninstall()
-
- # Called when the subscription is rejected by the server.
- rejected: ->
- @uninstall()
-
- appear: ->
- # Calls `AppearanceChannel#appear(data)` on the server.
- @perform("appear", appearing_on: $("main").data("appearing-on"))
-
- away: ->
- # Calls `AppearanceChannel#away` on the server.
- @perform("away")
-
-
- buttonSelector = "[data-behavior~=appear_away]"
-
- install: ->
- $(document).on "turbolinks:load.appearance", =>
- @appear()
-
- $(document).on "click.appearance", buttonSelector, =>
- @away()
- false
-
- $(buttonSelector).show()
-
- uninstall: ->
- $(document).off(".appearance")
- $(buttonSelector).hide()
+```js
+// app/javascript/channels/appearance_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create("AppearanceChannel", {
+ // Called once when the subscription is created.
+ initialized() {
+ this.update = this.update.bind(this)
+ },
+
+ // Called when the subscription is ready for use on the server.
+ connected() {
+ this.install()
+ this.update()
+ },
+
+ // Called when the WebSocket connection is closed.
+ disconnected() {
+ this.uninstall()
+ },
+
+ // Called when the subscription is rejected by the server.
+ rejected() {
+ this.uninstall()
+ },
+
+ update() {
+ this.documentIsActive ? this.appear() : this.away()
+ },
+
+ appear() {
+ // Calls `AppearanceChannel#appear(data)` on the server.
+ this.perform("appear", { appearing_on: this.appearingOn })
+ },
+
+ away() {
+ // Calls `AppearanceChannel#away` on the server.
+ this.perform("away")
+ },
+
+ install() {
+ window.addEventListener("focus", this.update)
+ window.addEventListener("blur", this.update)
+ document.addEventListener("turbolinks:load", this.update)
+ document.addEventListener("visibilitychange", this.update)
+ },
+
+ uninstall() {
+ window.removeEventListener("focus", this.update)
+ window.removeEventListener("blur", this.update)
+ document.removeEventListener("turbolinks:load", this.update)
+ document.removeEventListener("visibilitychange", this.update)
+ },
+
+ get documentIsActive() {
+ return document.visibilityState == "visible" && document.hasFocus()
+ },
+
+ get appearingOn() {
+ const element = document.querySelector("[data-appearing-on]")
+ return element ? element.getAttribute("data-appearing-on") : null
+ }
+})
```
##### Client-Server Interaction
@@ -445,16 +520,16 @@ ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The
**Server** identifies this connection by `current_user`.
2. **Client** subscribes to the appearance channel via
-`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`)
+`consumer.subscriptions.create({ channel: "AppearanceChannel" })`. (`appearance_channel.js`)
3. **Server** recognizes a new subscription has been initiated for the
appearance channel and runs its `subscribed` callback, calling the `appear`
method on `current_user`. (`appearance_channel.rb`)
4. **Client** recognizes that a subscription has been established and calls
-`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`.
-`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
-data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is
+`connected` (`appearance_channel.js`) which in turn calls `install` and `appear`.
+`appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
+data hash of `{ appearing_on: this.appearingOn }`. This is
possible because the server-side channel instance automatically exposes all
public methods declared on the class (minus the callbacks), so that these can be
reached as remote procedure calls via a subscription's `perform` method.
@@ -488,13 +563,17 @@ end
Create the client-side web notifications channel subscription:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
-# Client-side which assumes you've already requested
-# the right to send web notifications.
-App.cable.subscriptions.create "WebNotificationsChannel",
- received: (data) ->
- new Notification data["title"], body: data["body"]
+```js
+// app/javascript/channels/web_notifications_channel.js
+// Client-side which assumes you've already requested
+// the right to send web notifications.
+import consumer from "./consumer"
+
+consumer.subscriptions.create("WebNotificationsChannel", {
+ received(data) {
+ new Notification(data["title"], body: data["body"])
+ }
+})
```
Broadcast content to a web notification channel instance from elsewhere in your
@@ -592,6 +671,21 @@ To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout
HEAD. This uses a URL or path typically set via `config.action_cable.url` in the
environment configuration files.
+### Worker Pool Configuration
+
+The worker pool is used to run connection callbacks and channel actions in
+isolation from the server's main thread. Action Cable allows the application
+to configure the number of simultaneously processed threads in the worker pool.
+
+```ruby
+config.action_cable.worker_pool_size = 4
+```
+
+Also, note that your server must provide at least the same number of database
+connections as you have workers. The default worker pool size is set to 4, so
+that means you have to make at least 4 database connections available.
+ You can change that in `config/database.yml` through the `pool` attribute.
+
### Other Configurations
The other common option to configure is the log tags applied to the
@@ -609,11 +703,6 @@ config.action_cable.log_tags = [
For a full list of all configuration options, see the
`ActionCable::Server::Configuration` class.
-Also, note that your server must provide at least the same number of database
-connections as you have workers. The default worker pool size is set to 4, so
-that means you have to make at least that available. You can change that in
-`config/database.yml` through the `pool` attribute.
-
## Running Standalone Cable Servers
### In App
@@ -629,10 +718,9 @@ class Application < Rails::Application
end
```
-You can use `App.cable = ActionCable.createConsumer()` to connect to the cable
-server if `action_cable_meta_tag` is invoked in the layout. A custom path is
-specified as first argument to `createConsumer` (e.g. `App.cable =
-ActionCable.createConsumer("/websocket")`).
+You can use `ActionCable.createConsumer()` to connect to the cable
+server if `action_cable_meta_tag` is invoked in the layout. Otherwise, A path is
+specified as first argument to `createConsumer` (e.g. `ActionCable.createConsumer("/websocket")`).
For every instance of your server you create and for every worker your server
spawns, you will also have a new instance of Action Cable, but the use of Redis
@@ -690,3 +778,8 @@ internally, irrespective of whether the application server is multi-threaded or
Accordingly, Action Cable works with popular servers like Unicorn, Puma, and
Passenger.
+
+## Testing
+
+You can find detailed instructions on how to test your Action Cable functionality in the
+[testing guide](testing.html#testing-action-cable).
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index aa746e4731..d0d84251e4 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -212,7 +212,7 @@ class PeopleController < ActionController::Base
end
# This will pass with flying colors as long as there's a person key
- # in the parameters, otherwise it'll raise a
+ # in the parameters, otherwise it'll raise an
# ActionController::ParameterMissing exception, which will get
# caught by ActionController::Base and turned into a 400 Bad
# Request error.
diff --git a/guides/source/action_mailbox_basics.md b/guides/source/action_mailbox_basics.md
new file mode 100644
index 0000000000..c90892d456
--- /dev/null
+++ b/guides/source/action_mailbox_basics.md
@@ -0,0 +1,398 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
+
+Action Mailbox Basics
+=====================
+
+This guide provides you with all you need to get started in receiving
+emails to your application.
+
+After reading this guide, you will know:
+
+* How to receive email within a Rails application.
+* How to configure Action Mailbox.
+* How to generate and route emails to a mailbox.
+* How to test incoming emails.
+
+--------------------------------------------------------------------------------
+
+Introduction
+------------
+
+Action Mailbox routes incoming emails to controller-like mailboxes for
+processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill,
+Postmark, and SendGrid. You can also handle inbound mails directly via the
+built-in Exim, Postfix, and Qmail ingresses.
+
+The inbound emails are turned into `InboundEmail` records using Active Record
+and feature lifecycle tracking, storage of the original email on cloud storage
+via Active Storage, and responsible data handling with
+on-by-default incineration.
+
+These inbound emails are routed asynchronously using Active Job to one or
+several dedicated mailboxes, which are capable of interacting directly
+with the rest of your domain model.
+
+## Setup
+
+Install migrations needed for `InboundEmail` and ensure Active Storage is set up:
+
+```bash
+$ rails action_mailbox:install
+$ rails db:migrate
+```
+
+## Configuration
+
+### Amazon SES
+
+Install the [`aws-sdk-sns`](https://rubygems.org/gems/aws-sdk-sns) gem:
+
+```ruby
+# Gemfile
+gem "aws-sdk-sns", ">= 1.9.0", require: false
+```
+
+Tell Action Mailbox to accept emails from SES:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :amazon
+```
+
+[Configure SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications.html)
+to deliver emails to your application via POST requests to
+`/rails/action_mailbox/amazon/inbound_emails`. If your application lived at
+`https://example.com`, you would specify the fully-qualified URL
+`https://example.com/rails/action_mailbox/amazon/inbound_emails`.
+
+### Exim
+
+Tell Action Mailbox to accept emails from an SMTP relay:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :relay
+```
+
+Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress.
+
+Use `rails credentials:edit` to add the password to your application's encrypted credentials under
+`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
+
+Configure Exim to pipe inbound emails to `bin/rails action_mailbox:ingress:exim`,
+providing the `URL` of the relay ingress and the `INGRESS_PASSWORD` you
+previously generated. If your application lived at `https://example.com`, the
+full command would look like this:
+
+```shell
+bin/rails action_mailbox:ingress:exim URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
+```
+
+### Mailgun
+
+Give Action Mailbox your
+[Mailgun API key](https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials)
+so it can authenticate requests to the Mailgun ingress.
+
+Use `rails credentials:edit` to add your API key to your application's
+encrypted credentials under `action_mailbox.mailgun_api_key`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ mailgun_api_key: ...
+```
+
+Alternatively, provide your API key in the `MAILGUN_INGRESS_API_KEY` environment
+variable.
+
+Tell Action Mailbox to accept emails from Mailgun:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :mailgun
+```
+
+[Configure Mailgun](https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages)
+to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`.
+If your application lived at `https://example.com`, you would specify the
+fully-qualified URL `https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime`.
+
+### Mandrill
+
+Give Action Mailbox your Mandrill API key so it can authenticate requests to
+the Mandrill ingress.
+
+Use `rails credentials:edit` to add your API key to your application's
+encrypted credentials under `action_mailbox.mandrill_api_key`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ mandrill_api_key: ...
+```
+
+Alternatively, provide your API key in the `MANDRILL_INGRESS_API_KEY`
+environment variable.
+
+Tell Action Mailbox to accept emails from Mandrill:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :mandrill
+```
+
+[Configure Mandrill](https://mandrill.zendesk.com/hc/en-us/articles/205583197-Inbound-Email-Processing-Overview)
+to route inbound emails to `/rails/action_mailbox/mandrill/inbound_emails`.
+If your application lived at `https://example.com`, you would specify
+the fully-qualified URL `https://example.com/rails/action_mailbox/mandrill/inbound_emails`.
+
+### Postfix
+
+Tell Action Mailbox to accept emails from an SMTP relay:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :relay
+```
+
+Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress.
+
+Use `rails credentials:edit` to add the password to your application's encrypted credentials under
+`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
+
+[Configure Postfix](https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script)
+to pipe inbound emails to `bin/rails action_mailbox:ingress:postfix`, providing
+the `URL` of the Postfix ingress and the `INGRESS_PASSWORD` you previously
+generated. If your application lived at `https://example.com`, the full command
+would look like this:
+
+```shell
+$ bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
+```
+
+### Postmark
+
+Tell Action Mailbox to accept emails from Postmark:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :postmark
+```
+
+Generate a strong password that Action Mailbox can use to authenticate
+requests to the Postmark ingress.
+
+Use `rails credentials:edit` to add the password to your application's
+encrypted credentials under `action_mailbox.ingress_password`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD`
+environment variable.
+
+[Configure Postmark inbound webhook](https://postmarkapp.com/manual#configure-your-inbound-webhook-url)
+to forward inbound emails to `/rails/action_mailbox/postmark/inbound_emails` with the username `actionmailbox`
+and the password you previously generated. If your application lived at `https://example.com`, you would
+configure Postmark with the following fully-qualified URL:
+
+```
+https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails
+```
+
+NOTE: When configuring your Postmark inbound webhook, be sure to check the box labeled **"Include raw email content in JSON payload"**.
+Action Mailbox needs the raw email content to work.
+
+### Qmail
+
+Tell Action Mailbox to accept emails from an SMTP relay:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :relay
+```
+
+Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress.
+
+Use `rails credentials:edit` to add the password to your application's encrypted credentials under
+`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
+
+Configure Qmail to pipe inbound emails to `bin/rails action_mailbox:ingress:qmail`,
+providing the `URL` of the relay ingress and the `INGRESS_PASSWORD` you
+previously generated. If your application lived at `https://example.com`, the
+full command would look like this:
+
+```shell
+bin/rails action_mailbox:ingress:qmail URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
+```
+
+### SendGrid
+
+Tell Action Mailbox to accept emails from SendGrid:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :sendgrid
+```
+
+Generate a strong password that Action Mailbox can use to authenticate
+requests to the SendGrid ingress.
+
+Use `rails credentials:edit` to add the password to your application's
+encrypted credentials under `action_mailbox.ingress_password`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD`
+environment variable.
+
+[Configure SendGrid Inbound Parse](https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/)
+to forward inbound emails to
+`/rails/action_mailbox/sendgrid/inbound_emails` with the username `actionmailbox`
+and the password you previously generated. If your application lived at `https://example.com`,
+you would configure SendGrid with the following URL:
+
+```
+https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails
+```
+
+NOTE: When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled **“Post the raw, full MIME message.”** Action Mailbox needs the raw MIME message to work.
+
+## Examples
+
+Configure basic routing:
+
+```ruby
+# app/mailboxes/application_mailbox.rb
+class ApplicationMailbox < ActionMailbox::Base
+ routing /^save@/i => :forwards
+ routing /@replies\./i => :replies
+end
+```
+
+Then set up a mailbox:
+
+```ruby
+# Generate new mailbox
+$ bin/rails generate mailbox forwards
+```
+
+```ruby
+# app/mailboxes/forwards_mailbox.rb
+class ForwardsMailbox < ApplicationMailbox
+ # Callbacks specify prerequisites to processing
+ before_processing :require_forward
+
+ def process
+ if forwarder.buckets.one?
+ record_forward
+ else
+ stage_forward_and_request_more_details
+ end
+ end
+
+ private
+ def require_forward
+ unless message.forward?
+ # Use Action Mailers to bounce incoming emails back to sender – this halts processing
+ bounce_with Forwards::BounceMailer.missing_forward(
+ inbound_email, forwarder: forwarder
+ )
+ end
+ end
+
+ def forwarder
+ @forwarder ||= Person.where(email_address: mail.from)
+ end
+
+ def record_forward
+ forwarder.buckets.first.record \
+ Forward.new forwarder: forwarder, subject: message.subject, content: mail.content
+ end
+
+ def stage_forward_and_request_more_details
+ Forwards::RoutingMailer.choose_project(mail).deliver_now
+ end
+end
+```
+
+## Incineration of InboundEmails
+
+By default, an InboundEmail that has been successfully processed will be
+incinerated after 30 days. This ensures you're not holding on to people's data
+willy-nilly after they may have canceled their accounts or deleted their
+content. The intention is that after you've processed an email, you should have
+extracted all the data you needed and turned it into domain models and content
+on your side of the application. The InboundEmail simply stays in the system
+for the extra time to provide debugging and forensics options.
+
+The actual incineration is done via the `IncinerationJob` that's scheduled
+to run after `config.action_mailbox.incinerate_after` time. This value is
+by default set to `30.days`, but you can change it in your production.rb
+configuration. (Note that this far-future incineration scheduling relies on
+your job queue being able to hold jobs for that long.)
+
+## Working with Action Mailbox in development
+
+It's helpful to be able to test incoming emails in development without actually
+sending and receiving real emails. To accomplish this, there's a conductor
+controller mounted at `/rails/conductor/action_mailbox/inbound_emails`,
+which gives you an index of all the InboundEmails in the system, their
+state of processing, and a form to create a new InboundEmail as well.
+
+## Testing mailboxes
+
+Example:
+
+```ruby
+class ForwardsMailboxTest < ActionMailbox::TestCase
+ test "directly recording a client forward for a forwarder and forwardee corresponding to one project" do
+ assert_difference -> { people(:david).buckets.first.recordings.count } do
+ receive_inbound_email_from_mail \
+ to: 'save@example.com',
+ from: people(:david).email_address,
+ subject: "Fwd: Status update?",
+ body: <<~BODY
+ --- Begin forwarded message ---
+ From: Frank Holland <frank@microsoft.com>
+
+ What's the status?
+ BODY
+ end
+
+ recording = people(:david).buckets.first.recordings.last
+ assert_equal people(:david), recording.creator
+ assert_equal "Status update?", recording.forward.subject
+ assert_match "What's the status?", recording.forward.content.to_s
+ end
+end
+```
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 041a427f7c..16db433bd4 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -3,13 +3,13 @@
Action Mailer Basics
====================
-This guide provides you with all you need to get started in sending and
-receiving emails from and to your application, and many internals of Action
+This guide provides you with all you need to get started in sending
+emails from and to your application, and many internals of Action
Mailer. It also covers how to test your mailers.
After reading this guide, you will know:
-* How to send and receive email within a Rails application.
+* How to send email within a Rails application.
* How to generate and edit an Action Mailer class and mailer view.
* How to configure Action Mailer for your environment.
* How to test your Action Mailer classes.
@@ -427,7 +427,7 @@ If you would like to render a template located outside of the default `app/views
```ruby
class UserMailer < ApplicationMailer
prepend_view_path "custom/path/to/mailer/view"
-
+
# This will try to load "custom/path/to/mailer/view/welcome_email" template
def welcome_email
# ...
@@ -651,48 +651,8 @@ class UserMailer < ApplicationMailer
end
```
-Receiving Emails
-----------------
-
-Receiving and parsing emails with Action Mailer can be a rather complex
-endeavor. Before your email reaches your Rails app, you would have had to
-configure your system to somehow forward emails to your app, which needs to be
-listening for that. So, to receive emails in your Rails app you'll need to:
-
-* Implement a `receive` method in your mailer.
-
-* Configure your email server to forward emails from the address(es) you would
- like your app to receive to `/path/to/app/bin/rails runner
- 'UserMailer.receive(STDIN.read)'`.
-
-Once a method called `receive` is defined in any mailer, Action Mailer will
-parse the raw incoming email into an email object, decode it, instantiate a new
-mailer, and pass the email object to the mailer `receive` instance
-method. Here's an example:
-
-```ruby
-class UserMailer < ApplicationMailer
- def receive(email)
- page = Page.find_by(address: email.to.first)
- page.emails.create(
- subject: email.subject,
- body: email.body
- )
-
- if email.has_attachments?
- email.attachments.each do |attachment|
- page.attachments.create({
- file: attachment,
- description: email.subject
- })
- end
- end
- end
-end
-```
-
Action Mailer Callbacks
----------------------------
+-----------------------
Action Mailer allows for you to specify a `before_action`, `after_action` and
`around_action`.
@@ -839,13 +799,14 @@ Mailer Testing
You can find detailed instructions on how to test your mailers in the
[testing guide](testing.html#testing-your-mailers).
-Intercepting Emails
+Intercepting and Observing Emails
-------------------
-There are situations where you need to edit an email before it's
-delivered. Fortunately Action Mailer provides hooks to intercept every
-email. You can register an interceptor to make modifications to mail messages
-right before they are handed to the delivery agents.
+Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to register classes that are called during the mail delivery life cycle of every email sent.
+
+### Intercepting Emails
+
+Interceptors allow you to make modifications to emails before they are handed off to the delivery agents. An interceptor class must implement the `:delivering_email(message)` method which will be called before the email is sent.
```ruby
class SandboxEmailInterceptor
@@ -869,3 +830,21 @@ NOTE: The example above uses a custom environment called "staging" for a
production like server but for testing purposes. You can read
[Creating Rails environments](configuring.html#creating-rails-environments)
for more information about custom Rails environments.
+
+### Observing Emails
+
+Observers give you access to the email message after it has been sent. An observer class must implement the `:delivered_email(message)` method, which will be called after the email is sent.
+
+```ruby
+class EmailDeliveryObserver
+ def self.delivered_email(message)
+ EmailDelivery.log(message)
+ end
+end
+```
+Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file
+`config/initializers/email_delivery_observer.rb`
+
+```ruby
+ActionMailer::Base.register_observer(EmailDeliveryObserver)
+```
diff --git a/guides/source/action_text_overview.md b/guides/source/action_text_overview.md
new file mode 100644
index 0000000000..08ec35e52a
--- /dev/null
+++ b/guides/source/action_text_overview.md
@@ -0,0 +1,99 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
+
+Action Text Overview
+====================
+
+This guide provides you with all you need to get started in handling
+rich text content.
+
+After reading this guide, you will know:
+
+* How to configure Action Text.
+* How to handle rich text content.
+* How to style rich text content.
+
+--------------------------------------------------------------------------------
+
+Introduction
+------------
+
+Action Text brings rich text content and editing to Rails. It includes
+the [Trix editor](https://trix-editor.org) that handles everything from formatting
+to links to quotes to lists to embedded images and galleries.
+The rich text content generated by the Trix editor is saved in its own
+RichText model that's associated with any existing Active Record model in the application.
+Any embedded images (or other attachments) are automatically stored using
+Active Storage and associated with the included RichText model.
+
+## Trix compared to other rich text editors
+
+Most WYSIWYG editors are wrappers around HTML’s `contenteditable` and `execCommand` APIs,
+designed by Microsoft to support live editing of web pages in Internet Explorer 5.5,
+and [eventually reverse-engineered](https://blog.whatwg.org/the-road-to-html-5-contenteditable#history)
+and copied by other browsers.
+
+Because these APIs were never fully specified or documented,
+and because WYSIWYG HTML editors are enormous in scope, each
+browser's implementation has its own set of bugs and quirks,
+and JavaScript developers are left to resolve the inconsistencies.
+
+Trix sidesteps these inconsistencies by treating contenteditable
+as an I/O device: when input makes its way to the editor, Trix converts that input
+into an editing operation on its internal document model, then re-renders
+that document back into the editor. This gives Trix complete control over what
+happens after every keystroke, and avoids the need to use execCommand at all.
+
+## Installation
+
+Run `rails action_text:install` to add the Yarn package and copy over the necessary migration.
+
+## Examples
+
+Adding a rich text field to an existing model:
+
+```ruby
+# app/models/message.rb
+class Message < ApplicationRecord
+ has_rich_text :content
+end
+```
+
+Then refer to this field in the form for the model:
+
+```erb
+<%# app/views/messages/_form.html.erb %>
+<%= form_with(model: message) do |form| %>
+ <div class="field">
+ <%= form.label :content %>
+ <%= form.rich_text_area :content %>
+ </div>
+<% end %>
+```
+
+And finally display the sanitized rich text on a page:
+
+```erb
+<%= @message.content %>
+```
+
+To accept the rich text content, all you have to do is permit the referenced attribute:
+
+```ruby
+class MessagesController < ApplicationController
+ def create
+ message = Message.create! params.require(:message).permit(:title, :content)
+ redirect_to message
+ end
+end
+```
+
+## Custom styling
+
+By default, the Action Text editor and content is styled by the Trix defaults.
+If you want to change these defaults, you'll want to remove
+the `app/assets/stylesheets/actiontext.css` linker and base your stylings on
+the [contents of that file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css).
+
+You can also style the HTML used for embedded images and other attachments (known as blobs).
+On installation, Action Text will copy over a partial to
+`app/views/active_storage/blobs/_blob.html.erb`, which you can specialize.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 4dc69ef911..0ebef46373 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -165,6 +165,7 @@ Here is a noncomprehensive list of documentation:
- [Sneakers](https://github.com/jondot/sneakers/wiki/How-To:-Rails-Background-Jobs-with-ActiveJob)
- [Sucker Punch](https://github.com/brandonhilkert/sucker_punch#active-job)
- [Queue Classic](https://github.com/QueueClassic/queue_classic#active-job)
+- [Delayed Job](https://github.com/collectiveidea/delayed_job#active-job)
Queues
------
@@ -289,7 +290,7 @@ style if the code inside your block is so short that it fits in a single line.
For example, you could send metrics for every job enqueued:
```ruby
-class ApplicationJob
+class ApplicationJob < ActiveJob::Base
before_enqueue { |job| $statsd.increment "#{job.class.name.underscore}.enqueue" }
end
```
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index b9e24099b1..5c1de97aa8 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -45,7 +45,7 @@ relationships of the objects in an application can be easily stored and
retrieved from a database without writing SQL statements directly and with less
overall database access code.
-NOTE: If you are not familiar enough with relational database management systems (RDBMS) or structured query language (SQL), please go through [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means. Understanding how relational databases work is crucial to understanding Active Records and Rails in general.
+NOTE: Basic knowledge of relational database management systems (RDBMS) and structured query language (SQL) is helpful in order to fully understand Active Record. Please refer to [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means if you would like to learn more.
### Active Record as an ORM Framework
@@ -105,9 +105,9 @@ depending on the purpose of these columns.
fields that Active Record will look for when you create associations between
your models.
* **Primary keys** - By default, Active Record will use an integer column named
- `id` as the table's primary key. When using [Active Record
- Migrations](active_record_migrations.html) to create your tables, this column will be
- automatically created.
+ `id` as the table's primary key (`bigint` for Postgres and MYSQL, `integer`
+ for SQLite). When using [Active Record Migrations](active_record_migrations.html)
+ to create your tables, this column will be automatically created.
There are also some optional column names that will add additional features
to Active Record instances:
@@ -202,6 +202,8 @@ class Product < ApplicationRecord
end
```
+NOTE: Active Record does not support using non-primary key columns named `id`.
+
CRUD: Reading and Writing Data
------------------------------
@@ -307,12 +309,12 @@ user = User.find_by(name: 'David')
user.destroy
```
-If you'd like to delete several records in bulk, you may use `destroy_all`
-method:
+If you'd like to delete several records in bulk, you may use `destroy_by`
+or `destroy_all` method:
```ruby
# find and delete all users named David
-User.where(name: 'David').destroy_all
+User.destroy_by(name: 'David')
# delete all users
User.destroy_all
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index ebdee446f9..614737c342 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -239,13 +239,12 @@ Skipping Callbacks
Just as with validations, it is also possible to skip callbacks by using the following methods:
-* `decrement`
+* `decrement!`
* `decrement_counter`
* `delete`
* `delete_all`
-* `increment`
+* `increment!`
* `increment_counter`
-* `toggle`
* `update_column`
* `update_columns`
* `update_all`
@@ -310,7 +309,7 @@ end
### Using `:if` and `:unless` with a `Proc`
-Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners:
+It is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners:
```ruby
class Order < ApplicationRecord
@@ -338,6 +337,20 @@ class Comment < ApplicationRecord
end
```
+### Combining Callback Conditions
+
+When multiple conditions define whether or not a callback should happen, an `Array` can be used. Moreover, you can apply both `:if` and `:unless` to the same callback.
+
+```ruby
+class Comment < ApplicationRecord
+ after_create :send_email_to_author,
+ if: [Proc.new { |c| c.user.allow_send_email? }, :author_wants_emails?],
+ unless: Proc.new { |c| c.article.ignore_comments? }
+end
+```
+
+The callback only runs when all the `:if` conditions and none of the `:unless` conditions are evaluated to `true`.
+
Callback Classes
----------------
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 4d195988f8..2c1796c464 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -126,7 +126,7 @@ generator to handle making it for you:
$ rails generate migration AddPartNumberToProducts
```
-This will create an empty but appropriately named migration:
+This will create an appropriately named empty migration:
```ruby
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
@@ -135,9 +135,14 @@ class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
end
```
-If the migration name is of the form "AddXXXToYYY" or "RemoveXXXFromYYY" and is
-followed by a list of column names and types then a migration containing the
-appropriate `add_column` and `remove_column` statements will be created.
+This generator can do much more than append a timestamp to the file name.
+Based on naming conventions and additional (optional) arguments it can
+also start fleshing out the migration.
+
+If the migration name is of the form "AddColumnToTable" or
+"RemoveColumnFromTable" and is followed by a list of column names and
+types then a migration containing the appropriate `add_column` and
+`remove_column` statements will be created.
```bash
$ rails generate migration AddPartNumberToProducts part_number:string
@@ -491,9 +496,6 @@ NOTE: Active Record only supports single column foreign keys. `execute` and
`structure.sql` are required to use composite foreign keys. See
[Schema Dumping and You](#schema-dumping-and-you).
-NOTE: The SQLite3 adapter doesn't support `add_foreign_key` since SQLite supports
-only [a limited subset of ALTER TABLE](https://www.sqlite.org/lang_altertable.html).
-
Removing a foreign key is easy as well:
```ruby
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 16c1567c69..536a7138e9 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -14,7 +14,7 @@ After reading this guide, you will know:
--------------------------------------------------------------------------------
-In order to use the PostgreSQL adapter you need to have at least version 9.1
+In order to use the PostgreSQL adapter you need to have at least version 9.3
installed. Older versions are not supported.
To get started with PostgreSQL have a look at the
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 18aa179d03..270696d38d 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -623,6 +623,8 @@ To select only a subset of fields from the result set, you can specify the subse
For example, to select only `viewable_by` and `locked` columns:
```ruby
+Client.select(:viewable_by, :locked)
+# OR
Client.select("viewable_by, locked")
```
@@ -1293,7 +1295,7 @@ This is because it is ambiguous whether they should appear on the parent record,
Scopes
------
-Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as `where`, `joins` and `includes`. All scope methods will return an `ActiveRecord::Relation` object which will allow for further methods (such as other scopes) to be called on it.
+Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as `where`, `joins` and `includes`. All scope bodies should return an `ActiveRecord::Relation` or `nil` to allow for further methods (such as other scopes) to be called on it.
To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called:
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index c98f24d786..0c57802188 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -538,7 +538,8 @@ end
If you want to be sure that an association is present, you'll need to test
whether the associated object itself is present, and not the foreign key used
-to map the association.
+to map the association. This way, it is not only checked that the foreign key
+is not empty but also that the referenced object exists.
```ruby
class LineItem < ApplicationRecord
@@ -933,7 +934,7 @@ end
### Using a Proc with `:if` and `:unless`
-Finally, it's possible to associate `:if` and `:unless` with a `Proc` object
+It is possible to associate `:if` and `:unless` with a `Proc` object
which will be called. Using a `Proc` object gives you the ability to write an
inline condition instead of a separate method. This option is best suited for
one-liners.
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
index d5387219f5..e3bb41ae32 100644
--- a/guides/source/active_storage_overview.md
+++ b/guides/source/active_storage_overview.md
@@ -434,7 +434,7 @@ original blob into the specified format and redirect to its new service
location.
```erb
-<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %>
+<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
```
To switch to the Vips processor, you would add the following to
@@ -489,8 +489,7 @@ directly from the client to the cloud.
Using the npm package:
```js
- import * as ActiveStorage from "activestorage"
- ActiveStorage.start()
+ require("@rails/activestorage").start()
```
2. Annotate file inputs with the direct upload URL.
@@ -616,7 +615,7 @@ of choice, instantiate a DirectUpload and call its create method. Create takes
a callback to invoke when the upload completes.
```js
-import { DirectUpload } from "activestorage"
+import { DirectUpload } from "@rails/activestorage"
const input = document.querySelector('input[type=file]')
@@ -635,7 +634,7 @@ input.addEventListener('change', (event) => {
input.value = null
})
-const uploadFile = (file) {
+const uploadFile = (file) => {
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const url = input.dataset.directUploadUrl
@@ -664,7 +663,7 @@ will call the object's `directUploadWillStoreFileWithXHR` method. You can then
bind your own progress handler on the XHR.
```js
-import { DirectUpload } from "activestorage"
+import { DirectUpload } from "@rails/activestorage"
class Uploader {
constructor(file, url) {
@@ -742,16 +741,22 @@ during the test are complete and you won't receive an error from Active Storage
saying it can't find a file.
```ruby
+module RemoveUploadedFiles
+ def after_teardown
+ super
+ remove_uploaded_files
+ end
+
+ private
+
+ def remove_uploaded_files
+ FileUtils.rm_rf(Rails.root.join('tmp', 'storage'))
+ end
+end
+
module ActionDispatch
class IntegrationTest
- def remove_uploaded_files
- FileUtils.rm_rf(Rails.root.join('tmp', 'storage'))
- end
-
- def after_teardown
- super
- remove_uploaded_files
- end
+ prepend RemoveUploadedFiles
end
end
```
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 6b0554bb5f..903f39994f 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1201,6 +1201,8 @@ The `inquiry` method converts a string into a `StringInquirer` object making equ
"active".inquiry.inactive? # => false
```
+NOTE: Defined in `active_support/core_ext/string/inquiry.rb`.
+
### `starts_with?` and `ends_with?`
Active Support defines 3rd person aliases of `String#start_with?` and `String#end_with?`:
@@ -2132,30 +2134,6 @@ The methods `second`, `third`, `fourth`, and `fifth` return the corresponding el
NOTE: Defined in `active_support/core_ext/array/access.rb`.
-### Adding Elements
-
-#### `prepend`
-
-This method is an alias of `Array#unshift`.
-
-```ruby
-%w(a b c d).prepend('e') # => ["e", "a", "b", "c", "d"]
-[].prepend(10) # => [10]
-```
-
-NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`.
-
-#### `append`
-
-This method is an alias of `Array#<<`.
-
-```ruby
-%w(a b c d).append('e') # => ["a", "b", "c", "d", "e"]
-[].append([1,2]) # => [[1, 2]]
-```
-
-NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`.
-
### Extracting
The method `extract!` removes and returns the elements for which the block returns a true value.
@@ -2646,48 +2624,6 @@ There's also the bang variant `except!` that removes keys in the very receiver.
NOTE: Defined in `active_support/core_ext/hash/except.rb`.
-#### `transform_keys` and `transform_keys!`
-
-The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver:
-
-```ruby
-{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase }
-# => {"" => nil, "1" => 1, "A" => :a}
-```
-
-In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
-
-```ruby
-{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase }
-# The result could either be
-# => {"A"=>2}
-# or
-# => {"A"=>1}
-```
-
-This method may be useful for example to build specialized conversions. For instance `stringify_keys` and `symbolize_keys` use `transform_keys` to perform their key conversions:
-
-```ruby
-def stringify_keys
- transform_keys { |key| key.to_s }
-end
-...
-def symbolize_keys
- transform_keys { |key| key.to_sym rescue key }
-end
-```
-
-There's also the bang variant `transform_keys!` that applies the block operations to keys in the very receiver.
-
-Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is:
-
-```ruby
-{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase }
-# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}}
-```
-
-NOTE: Defined in `active_support/core_ext/hash/keys.rb`.
-
#### `stringify_keys` and `stringify_keys!`
The method `stringify_keys` returns a hash that has a stringified version of the keys in the receiver. It does so by sending `to_s` to them:
@@ -2795,26 +2731,7 @@ NOTE: Defined in `active_support/core_ext/hash/keys.rb`.
### Slicing
-Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes:
-
-```ruby
-{a: 1, b: 2, c: 3}.slice(:a, :c)
-# => {:a=>1, :c=>3}
-
-{a: 1, b: 2, c: 3}.slice(:b, :X)
-# => {:b=>2} # non-existing keys are ignored
-```
-
-If the receiver responds to `convert_key` keys are normalized:
-
-```ruby
-{a: 1, b: 2}.with_indifferent_access.slice("a")
-# => {:a=>1}
-```
-
-NOTE. Slicing may come in handy for sanitizing option hashes with a white list of keys.
-
-There's also `slice!` which in addition to perform a slice in place returns what's removed:
+The method `slice!` replaces the hash with only the given keys and returns a hash containing the removed key/value pairs.
```ruby
hash = {a: 1, b: 2}
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 64db141381..c1c3832b79 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -255,13 +255,16 @@ Active Record
### sql.active_record
-| Key | Value |
-| ---------------- | ---------------------------------------- |
-| `:sql` | SQL statement |
-| `:name` | Name of the operation |
-| `:connection_id` | `self.object_id` |
-| `:binds` | Bind parameters |
-| `:cached` | `true` is added when cached queries used |
+| Key | Value |
+| -------------------- | ---------------------------------------- |
+| `:sql` | SQL statement |
+| `:name` | Name of the operation |
+| `:connection_id` | Object ID of the connection object |
+| `:connection` | Connection object |
+| `:binds` | Bind parameters |
+| `:type_casted_binds` | Typecasted bind parameters |
+| `:statement_name` | SQL Statement name |
+| `:cached` | `true` is added when cached queries used |
INFO. The adapters will add their own data as well.
@@ -270,7 +273,10 @@ INFO. The adapters will add their own data as well.
sql: "SELECT \"posts\".* FROM \"posts\" ",
name: "Post Load",
connection_id: 70307250813140,
- binds: []
+ connection: #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>,
+ binds: [#<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
+ type_casted_binds: [11],
+ statement_name: nil
}
```
@@ -291,32 +297,6 @@ INFO. The adapters will add their own data as well.
Action Mailer
-------------
-### receive.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 |
-
-```ruby
-{
- mailer: "Notification",
- message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
- subject: "Rails Guides",
- to: ["users@rails.com", "dhh@rails.com"],
- from: ["me@rails.com"],
- date: Sat, 10 Mar 2012 14:18:09 +0100,
- mail: "..." # omitted for brevity
-}
-```
-
### deliver.action_mailer
| Key | Value |
@@ -648,6 +628,18 @@ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*a
end
```
+You may also pass block with only one argument, it will yield an event object to the block:
+
+```ruby
+ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event|
+ event.name # => "process_action.action_controller"
+ event.duration # => 10 (in milliseconds)
+ event.payload # => {:extra=>information}
+
+ Rails.logger.info "#{event} Received!"
+end
+```
+
Most times you only care about the data itself. Here is a shortcut to just get the data.
```ruby
@@ -672,7 +664,7 @@ Creating custom events
Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of
all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block.
The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times
-and add the instrumenter's unique ID. All data passed into the `instrument` call will make
+and add the instrumenter's unique ID. All data passed into the `instrument` call will make
it into the payload.
Here's an example:
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 85367c50e7..870f5f7b87 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -374,7 +374,7 @@ controller modules by default:
- `ActionController::Renderers::All`: Support for `render :json` and friends.
- `ActionController::ConditionalGet`: Support for `stale?`.
- `ActionController::BasicImplicitRender`: Makes sure to return an empty response, if there isn't an explicit one.
-- `ActionController::StrongParameters`: Support for parameters white-listing in combination with Active Model mass assignment.
+- `ActionController::StrongParameters`: Support for parameters filtering in combination with Active Model mass assignment.
- `ActionController::DataStreaming`: Support for `send_file` and `send_data`.
- `AbstractController::Callbacks`: Support for `before_action` and
similar helpers.
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 66cf9da33b..9f8edea598 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -233,11 +233,6 @@ code for JavaScript plugins and CSS frameworks. Keep in mind that third party
code with references to other files also processed by the asset Pipeline (images,
stylesheets, etc.), will need to be rewritten to use helpers like `asset_path`.
-WARNING: If you are upgrading from Rails 3, please take into account that assets
-under `lib/assets` or `vendor/assets` are available for inclusion via the
-application manifests but no longer part of the precompile array. See
-[Precompiling Assets](#precompiling-assets) for guidance.
-
#### Search Paths
When a file is referenced from a manifest or a helper, Sprockets searches the
@@ -750,7 +745,7 @@ mapping requests back to Sprockets. A typical manifest file looks like:
The default location for the manifest is the root of the location specified in
`config.assets.prefix` ('/assets' by default).
-NOTE: If there are missing precompiled files in production you will get an
+NOTE: If there are missing precompiled files in production you will get a
`Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError`
exception indicating the name of the missing file(s).
@@ -1106,7 +1101,7 @@ Windows you have a JavaScript runtime installed in your operating system.
-### Serving GZipped version of assets
+### GZipping your assets
By default, gzipped version of compiled assets will be generated, along with
the non-gzipped version of assets. Gzipped assets help reduce the transmission
@@ -1116,6 +1111,8 @@ of data over the wire. You can configure this by setting the `gzip` flag.
config.assets.gzip = false # disable gzipped assets generation
```
+Refer to your web server's documentation for instructions on how to serve gzipped assets.
+
### Using Your Own Compressor
The compressor config settings for CSS and JavaScript also take any object.
@@ -1234,60 +1231,3 @@ it as a preprocessor for your mime type.
Sprockets.register_preprocessor 'text/css', AddComment
```
-Upgrading from Old Versions of Rails
-------------------------------------
-
-There are a few issues when upgrading from Rails 3.0 or Rails 2.x. The first is
-moving the files from `public/` to the new locations. See [Asset
-Organization](#asset-organization) above for guidance on the correct locations
-for different file types.
-
-Next is updating the various environment files with the correct default
-options.
-
-In `application.rb`:
-
-```ruby
-# Version of your assets, change this if you want to expire all your assets
-config.assets.version = '1.0'
-
-# Change the path that assets are served from config.assets.prefix = "/assets"
-```
-
-In `development.rb`:
-
-```ruby
-# Expands the lines which load the assets
-config.assets.debug = true
-```
-
-And in `production.rb`:
-
-```ruby
-# Choose the compressors to use (if any)
-config.assets.js_compressor = :uglifier
-# config.assets.css_compressor = :yui
-
-# Don't fallback to assets pipeline if a precompiled asset is missed
-config.assets.compile = false
-
-# Generate digests for assets URLs.
-config.assets.digest = true
-
-# Precompile additional assets (application.js, application.css, and all
-# non-JS/CSS are already added)
-# config.assets.precompile += %w( admin.js admin.css )
-```
-
-Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so
-`test.rb` now requires Sprockets configuration. The old defaults in the test
-environment are: `config.assets.compile = true`, `config.assets.compress = false`,
-`config.assets.debug = false` and `config.assets.digest = false`.
-
-The following should also be added to your `Gemfile`:
-
-```ruby
-gem 'sass-rails', "~> 3.2.3"
-gem 'coffee-rails', "~> 3.2.1"
-gem 'uglifier'
-```
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 78a1f47407..38e46e4bf8 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1257,8 +1257,8 @@ Controls what happens to the associated object when its owner is destroyed:
* `:destroy` causes the associated object to also be destroyed
* `:delete` causes the associated object to be deleted directly from the database (so callbacks will not execute)
-* `:nullify` causes the foreign key to be set to `NULL`. Callbacks are not executed.
-* `:restrict_with_exception` causes an exception to be raised if there is an associated record
+* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed.
+* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there is an associated record
* `:restrict_with_error` causes an error to be added to the owner if there is an associated object
It's necessary not to set or leave `:nullify` option for those associations
@@ -1658,10 +1658,12 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:destroy` causes all the associated objects to also be destroyed
* `:delete_all` causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
-* `:nullify` causes the foreign keys to be set to `NULL`. Callbacks are not executed.
-* `:restrict_with_exception` causes an exception to be raised if there are any associated records
+* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed.
+* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there are any associated records
* `:restrict_with_error` causes an error to be added to the owner if there are any associated objects
+The `:destroy` and `:delete_all` options also affect the semantics of the `collection.delete` and `collection=` methods by causing them to destroy associated objects when they are removed from the collection.
+
##### `:foreign_key`
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 321eee637f..56c0ca78a0 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -51,10 +51,10 @@ For instance, it will not impact low-level caching, that we address
### Page Caching
Page caching is a Rails mechanism which allows the request for a generated page
-to be fulfilled by the webserver (i.e. Apache or NGINX) without having to go
+to be fulfilled by the web server (i.e. Apache or NGINX) without having to go
through the entire Rails stack. While this is super fast it can't be applied to
every situation (such as pages that need authentication). Also, because the
-webserver is serving a file directly from the filesystem you will need to
+web server is serving a file directly from the filesystem you will need to
implement cache expiration.
INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching).
@@ -295,14 +295,14 @@ Consider the following example. An application has a `Product` model with an ins
```ruby
class Product < ApplicationRecord
def competing_price
- Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
+ Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
```
-NOTE: Notice that in this example we used the `cache_key` method, so the resulting cache key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model's `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
+NOTE: Notice that in this example we used the `cache_key_with_version` method, so the resulting cache key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key_with_version` generates a string based on the model's `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
### SQL Caching
@@ -563,7 +563,7 @@ class ProductsController < ApplicationController
# If the request is stale according to the given timestamp and etag value
# (i.e. it needs to be processed again) then execute this block
- if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key)
+ if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
respond_to do |wants|
# ... normal response processing
end
@@ -577,7 +577,7 @@ class ProductsController < ApplicationController
end
```
-Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
+Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key_with_version` methods for setting `last_modified` and `etag`:
```ruby
class ProductsController < ApplicationController
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 5fd3ad17de..bbebf97c3f 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -36,35 +36,35 @@ $ rails --help
Usage: rails COMMAND [ARGS]
The most common rails commands are:
-generate Generate new code (short-cut alias: "g")
-console Start the Rails console (short-cut alias: "c")
-server Start the Rails server (short-cut alias: "s")
-...
+ generate Generate new code (short-cut alias: "g")
+ console Start the Rails console (short-cut alias: "c")
+ server Start the Rails server (short-cut alias: "s")
+ ...
All commands can be run with -h (or --help) for more information.
In addition to those commands, there are:
-about List versions of all Rails ...
-assets:clean[keep] Remove old compiled assets
-assets:clobber Remove compiled assets
-assets:environment Load asset compile environment
-assets:precompile Compile all the assets ...
-...
-db:fixtures:load Loads fixtures into the ...
-db:migrate Migrate the database ...
-db:migrate:status Display status of migrations
-db:rollback Rolls the schema back to ...
-db:schema:cache:clear Clears a db/schema_cache.yml file
-db:schema:cache:dump Creates a db/schema_cache.yml file
-db:schema:dump Creates a db/schema.rb file ...
-db:schema:load Loads a schema.rb file ...
-db:seed Loads the seed data ...
-db:structure:dump Dumps the database structure ...
-db:structure:load Recreates the databases ...
-db:version Retrieves the current schema ...
-...
-restart Restart app by touching ...
-tmp:create Creates tmp directories ...
+ about List versions of all Rails ...
+ assets:clean[keep] Remove old compiled assets
+ assets:clobber Remove compiled assets
+ assets:environment Load asset compile environment
+ assets:precompile Compile all the assets ...
+ ...
+ db:fixtures:load Loads fixtures into the ...
+ db:migrate Migrate the database ...
+ db:migrate:status Display status of migrations
+ db:rollback Rolls the schema back to ...
+ db:schema:cache:clear Clears a db/schema_cache.yml file
+ db:schema:cache:dump Creates a db/schema_cache.yml file
+ db:schema:dump Creates a db/schema.rb file ...
+ db:schema:load Loads a schema.rb file ...
+ db:seed Loads the seed data ...
+ db:structure:dump Dumps the database structure ...
+ db:structure:load Recreates the databases ...
+ db:version Retrieves the current schema ...
+ ...
+ restart Restart app by touching ...
+ tmp:create Creates tmp directories ...
```
Let's create a simple Rails application to step through each of these commands in context.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 61bb35cf93..ad82614a0d 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -379,27 +379,13 @@ The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
-The SQLite3Adapter adapter adds one additional configuration option:
+The PostgreSQL adapter adds one additional configuration option:
-* `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
-indicates whether boolean values are stored in sqlite3 databases as 1 and 0 or
-'t' and 'f'. Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
-set to false is deprecated. SQLite databases have used 't' and 'f' to serialize
-boolean values and must have old data converted to 1 and 0 (its native boolean
-serialization) before setting this flag to true. Conversion can be accomplished
-by setting up a Rake task which runs
-
- ```ruby
- ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
- ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)
- ```
-
- for all models and all boolean columns, after which the flag must be set to true
-by adding the following to your `application.rb` file:
-
- ```ruby
- Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
- ```
+* `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables`
+ controls whether database tables created should be "unlogged," which can speed
+ up performance but adds a risk of data loss if the database crashes. It is
+ highly recommended that you do not enable this in a production environment.
+ Defaults to `false` in all environments.
The schema dumper adds two additional configuration options:
@@ -433,7 +419,7 @@ The schema dumper adds two additional configuration options:
* `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for.
-* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default, but enabled when loading defaults for Rails 5.2.
+* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default.
* `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`.
@@ -604,12 +590,28 @@ Defaults to `'signed cookie'`.
* `config.action_view.default_enforce_utf8` determines whether forms are generated with a hidden tag that forces older versions of Internet Explorer to submit forms encoded in UTF-8. This defaults to `false`.
-* `config.action_view.finalize_compiled_template_methods` determines
- whether the methods on `ActionView::CompiledTemplates` that templates
- compile themselves to are removed when template instances are
- destroyed by the garbage collector. This helps prevent memory leaks in
- development mode, but for large test suites, disabling this option in
- the test environment can improve performance. This defaults to `true`.
+
+### Configuring Action Mailbox
+
+`config.action_mailbox` provides the following configuration options:
+
+* `config.action_mailbox.logger` contains the logger used by Action Mailbox. It accepts a logger conforming to the interface of Log4r or the default Ruby Logger class. The default is `Rails.logger`.
+
+ ```ruby
+ config.action_mailbox.logger = ActiveSupport::Logger.new(STDOUT)
+ ```
+
+* `config.action_mailbox.incinerate_after` accepts an `ActiveSupport::Duration` indicating how long after processing `ActionMailbox::InboundEmail` records should be destroyed. It defaults to `30.days`.
+
+ ```ruby
+ # Incinerate inbound emails 14 days after processing.
+ config.action_mailbox.incinerate_after = 14.days
+ ```
+
+* `config.action_mailbox.queues.incineration` accepts a symbol indicating the Active Job queue to use for incineration jobs. It defaults to `:action_mailbox_incineration`.
+
+* `config.action_mailbox.queues.routing` accepts a symbol indicating the Active Job queue to use for routing jobs. It defaults to `:action_mailbox_routing`.
+
### Configuring Action Mailer
@@ -690,6 +692,8 @@ There are a number of settings available on `config.action_mailer`:
* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is `false` in all environments.
+* `config.action_mailer.delivery_job` specifies delivery job for mail. Defaults to `ActionMailer::DeliveryJob`.
+
### Configuring Active Support
@@ -707,7 +711,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.use_sha1_digests` specifies whether to use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. Defaults to false.
-* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default, but enabled when loading defaults for Rails 5.2.
+* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default.
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
@@ -723,7 +727,7 @@ There are a few configuration options available in Active Support:
`config.active_job` provides the following configuration options:
-* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
+* `config.active_job.queue_adapter` sets the adapter for the queuing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
```ruby
# Be sure to have the adapter's gem in your Gemfile
@@ -774,6 +778,8 @@ There are a few configuration options available in Active Support:
* `config.active_job.custom_serializers` allows to set custom argument serializers. Defaults to `[]`.
+* `config.active_job.return_false_on_aborted_enqueue` change the return value of `#enqueue` to false instead of the job instance when the enqueuing is aborted. Defaults to `false`.
+
### Configuring Action Cable
* `config.action_cable.url` accepts a string for the URL for where
@@ -785,6 +791,9 @@ main application.
You can set this as nil to not mount Action Cable as part of your
normal Rails server.
+You can find more detailed configuration options in the
+[Action Cable Overview](action_cable_overview.html#configuration).
+
### Configuring Active Storage
@@ -805,15 +814,21 @@ normal Rails server.
config.active_storage.paths[:ffprobe] = '/usr/local/bin/ffprobe'
```
-* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop image/vnd.microsoft.icon)`.
+* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/pjpeg image/tiff image/vnd.adobe.photoshop image/vnd.microsoft.icon)`.
* `config.active_storage.content_types_to_serve_as_binary` accepts an array of strings indicating the content types that Active Storage will always serve as an attachment, rather than inline. The default is `%w(text/html
text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml)`.
-* `config.active_storage.queue` can be used to set the name of the Active Job queue used to perform jobs like analyzing the content of a blob or purging a blog.
+* `config.active_storage.queues.analysis` accepts a symbol indicating the Active Job queue to use for analysis jobs. When this option is `nil`, analysis jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`).
+
+ ```ruby
+ config.active_storage.queues.analysis = :low_priority
+ ```
+
+* `config.active_storage.queues.purge` accepts a symbol indicating the Active Job queue to use for purge jobs. When this option is `nil`, purge jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`).
```ruby
- config.active_storage.queue = :low_priority
+ config.active_storage.queues.purge = :low_priority
```
* `config.active_storage.logger` can be used to set the logger used by Active Storage. Accepts a logger conforming to the interface of Log4r or the default Ruby Logger class.
@@ -837,6 +852,39 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla
The default is `/rails/active_storage`
+### Results of `load_defaults`
+
+#### With '5.0':
+
+- `config.action_controller.per_form_csrf_tokens`: `true`
+- `config.action_controller.forgery_protection_origin_check`: `true`
+- `ActiveSupport.to_time_preserves_timezone`: `true`
+- `config.active_record.belongs_to_required_by_default`: `true`
+- `config.ssl_options`: `{ hsts: { subdomains: true } }`
+
+#### With '5.1':
+
+- `config.assets.unknown_asset_fallback`: `false`
+- `config.action_view.form_with_generates_remote_forms`: `true`
+
+#### With '5.2':
+
+- `config.active_record.cache_versioning`: `true`
+- `action_dispatch.use_authenticated_cookie_encryption`: `true`
+- `config.active_support.use_authenticated_message_encryption`: `true`
+- `config.active_support.use_sha1_digests`: `true`
+- `config.action_controller.default_protect_from_forgery`: `true`
+- `config.action_view.form_with_generates_ids`: `true`
+
+#### With '6.0':
+
+- `config.action_view.default_enforce_utf8`: `false`
+- `config.action_dispatch.use_cookies_with_metadata`: `true`
+- `config.action_mailer.delivery_job`: `"ActionMailer::MailDeliveryJob"`
+- `config.active_job.return_false_on_aborted_enqueue`: `true`
+- `config.active_storage.queues.analysis`: `:active_storage_analysis`
+- `config.active_storage.queues.purge`: `:active_storage_purge`
+
### Configuring a Database
Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`.
@@ -1011,6 +1059,7 @@ If you choose to use MySQL or MariaDB instead of the shipped SQLite3 database, y
```yaml
development:
adapter: mysql2
+ encoding: utf8mb4
database: blog_development
pool: 5
username: root
@@ -1186,6 +1235,8 @@ Using Initializer Files
After loading the framework and any gems in your application, Rails turns to loading initializers. An initializer is any Ruby file stored under `config/initializers` in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks and gems are loaded, such as options to configure settings for these parts.
+NOTE: There is no guarantee that your initializers will run after all the gem initializers, so any initialization code that depends on a given gem having been initialized should go into a `config.after_initialize` block.
+
NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down.
TIP: While Rails supports numbering of initializer file names for load ordering purposes, a better technique is to place any code that need to load in a specific order within the same file. This reduces file name churn, makes dependencies more explicit, and can help surface new concepts within your application.
@@ -1393,7 +1444,7 @@ Custom configuration
You can configure your own code through the Rails configuration object with
custom configuration under either the `config.x` namespace, or `config` directly.
The key difference between these two is that you should be using `config.x` if you
-are defining _nested_ configuration (ex: `config.x.nested.nested.hi`), and just
+are defining _nested_ configuration (ex: `config.x.nested.hi`), and just
`config` for _single level_ configuration (ex: `config.hello`).
```ruby
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index ed47a0de0f..709a5146e9 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -324,6 +324,26 @@ $ cd actionmailer
$ bundle exec rake test
```
+#### For a Specific Directory
+
+If you want to run the tests located in a specific directory use the `TEST_DIR`
+environment variable. For example, this will run the tests in the
+`railties/test/generators` directory only:
+
+```bash
+$ cd railties
+$ TEST_DIR=generators bundle exec rake test
+```
+
+#### For a Specific File
+
+You can run the tests for a particular file by using:
+
+```bash
+$ cd actionpack
+$ bundle exec ruby -w -Itest test/template/form_helper_test.rb
+```
+
#### Running a Single Test
You can run a single test through ruby. For instance:
@@ -333,8 +353,7 @@ $ cd actionmailer
$ bundle exec ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
```
-The `-n` option allows you to run a single method instead of the whole
-file.
+The `-n` option allows you to run a single method instead of the whole file.
#### Running tests with a specific seed
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 7f7766e7d7..3a383cbd4d 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -232,7 +232,7 @@ irb(main):003:0> Article.pamplemousse
=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">
```
-Below each database statement you can see arrows pointing to the specific source filename (and line number) of the method that resulted in a database call. This can help you identity and address performance problems caused by N+1 queries: single database queries that generates multiple additional queries.
+Below each database statement you can see arrows pointing to the specific source filename (and line number) of the method that resulted in a database call. This can help you identify and address performance problems caused by N+1 queries: single database queries that generates multiple additional queries.
Verbose query logs are enabled by default in the development environment logs after Rails 5.2.
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 07538a1cb7..e84e5561f2 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -8,8 +8,6 @@ This guide covers how to setup an environment for Ruby on Rails core development
After reading this guide, you will know:
* How to set up your machine for Rails development
-* How to run specific groups of unit tests from the Rails test suite
-* How the Active Record portion of the Rails test suite operates
--------------------------------------------------------------------------------
@@ -43,195 +41,131 @@ $ git clone https://github.com/rails/rails.git
$ cd rails
```
-### Set up and Run the Tests
+### Install Additional Tools and Services
-The test suite must pass with any submitted code. No matter whether you are writing a new patch, or evaluating someone else's, you need to be able to run the tests.
+Some Rails tests depend on additional tools that you need to install before running those specific tests.
-Install first SQLite3 and its development files for the `sqlite3` gem. On macOS
-users are done with:
+Here's the list of each gems' additional dependencies:
-```bash
-$ brew install sqlite3
-```
-
-In Ubuntu you're done with just:
-
-```bash
-$ sudo apt-get install sqlite3 libsqlite3-dev
-```
-
-If you are on Fedora or CentOS, you're done with
-
-```bash
-$ sudo yum install libsqlite3x libsqlite3x-devel
-```
-
-If you are on Arch Linux, you will need to run:
-
-```bash
-$ sudo pacman -S sqlite
-```
-
-For FreeBSD users, you're done with:
-
-```bash
-# pkg install sqlite3
-```
+* Action Cable depends on Redis
+* Active Record depends on SQLite3, MySQL and PostgreSQL
+* Active Storage depends on Yarn (additionally Yarn depends on
+ [Node.js](https://nodejs.org/)), ImageMagick, FFmpeg, muPDF, and on macOS
+ also XQuartz and Poppler.
+* Active Support depends on memcached and Redis
+* Railties depend on a JavaScript runtime environment, such as having
+ [Node.js](https://nodejs.org/) installed.
-Or compile the `databases/sqlite3` port.
+Install all the services you need to properly test the full gem you'll be
+making changes to.
-Get a recent version of [Bundler](https://bundler.io/)
+NOTE: Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](https://redis.io/download#installation).
-```bash
-$ gem install bundler
-$ gem update bundler
-```
+NOTE: Active Record tests _must_ pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against single adapter.
-and run:
+Below you can find instructions on how to install all of the additional
+tools for different OSes.
-```bash
-$ bundle install --without db
-```
-
-This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon.
+#### macOS
-NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running.
+On macOS you can use [Homebrew](https://brew.sh/) to install all of the
+additional tools.
-You can use [Homebrew](https://brew.sh/) to install memcached on macOS:
+To install all run:
```bash
-$ brew install memcached
+$ brew bundle
```
-On Ubuntu you can install it with apt-get:
+You'll also need to start each of the installed services. To list all
+available services run:
```bash
-$ sudo apt-get install memcached
+$ brew services list
```
-Or use yum on Fedora or CentOS:
+You can then start each of the services one by one like this:
```bash
-$ sudo yum install memcached
+$ brew services start mysql
```
-If you are running on Arch Linux:
-
-```bash
-$ sudo pacman -S memcached
-```
-
-For FreeBSD users, you're done with:
-
-```bash
-# pkg install memcached
-```
+Replace `mysql` with the name of the service you want to start.
-Alternatively, you can compile the `databases/memcached` port.
+#### Ubuntu
-With the dependencies now installed, you can run the test suite with:
+To install all run:
```bash
-$ bundle exec rake test
-```
-
-You can also run tests for a specific component, like Action Pack, by going into its directory and executing the same command:
+$ sudo apt-get update
+$ sudo apt-get install sqlite3 libsqlite3-dev
+ mysql-server libmysqlclient-dev
+ postgresql postgresql-client postgresql-contrib libpq-dev
+ redis-server memcached imagemagick ffmpeg mupdf mupdf-tools
-```bash
-$ cd actionpack
-$ bundle exec rake test
+# Install Yarn
+$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+$ sudo apt-get install yarn
```
-If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests in the `railties/test/generators` directory only:
-
-```bash
-$ cd railties
-$ TEST_DIR=generators bundle exec rake test
-```
+#### Fedora or CentOS
-You can run the tests for a particular file by using:
+To install all run:
```bash
-$ cd actionpack
-$ bundle exec ruby -Itest test/template/form_helper_test.rb
-```
-
-Or, you can run a single test in a particular file:
+$ sudo dnf install sqlite-devel sqlite-libs
+ mysql-server mysql-devel
+ postgresql-server postgresql-devel
+ redis memcached imagemagick ffmpeg mupdf
-```bash
-$ cd actionpack
-$ bundle exec ruby -Itest path/to/test.rb -n test_name
+# Install Yarn
+# Use this command if you do not have Node.js installed
+$ curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -
+# If you have Node.js installed, use this command instead
+$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
+$ sudo dnf install yarn
```
-### Railties Setup
-
-Some Railties tests depend on a JavaScript runtime environment, such as having [Node.js](https://nodejs.org/) installed.
-
-### Active Record Setup
-
-Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them.
-
-WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
-
-#### Database Configuration
-
-The Active Record test suite requires a custom config file: `activerecord/test/config.yml`. An example is provided in `activerecord/test/config.example.yml` which can be copied and used as needed for your environment.
+#### Arch Linux
-#### MySQL and PostgreSQL
-
-To be able to run the suite for MySQL and PostgreSQL we need their gems. Install
-first the servers, their client libraries, and their development files.
-
-On macOS, you can run:
-
-```bash
-$ brew install mysql
-$ brew install postgresql
-```
-
-Follow the instructions given by Homebrew to start these.
-
-On Ubuntu, just run:
-
-```bash
-$ sudo apt-get install mysql-server libmysqlclient-dev
-$ sudo apt-get install postgresql postgresql-client postgresql-contrib libpq-dev
-```
-
-On Fedora or CentOS, just run:
+To install all run:
```bash
-$ sudo yum install mysql-server mysql-devel
-$ sudo yum install postgresql-server postgresql-devel
+$ sudo pacman -S sqlite
+ mariadb libmariadbclient mariadb-clients
+ postgresql postgresql-libs
+ redis memcached imagemagick ffmpeg mupdf mupdf-tools poppler
+ yarn
+$ sudo systemctl start redis
```
-If you are running Arch Linux, MySQL isn't supported anymore so you will need to
-use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)):
+NOTE: If you are running Arch Linux, MySQL isn't supported anymore so you will need to
+use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)).
-```bash
-$ sudo pacman -S mariadb libmariadbclient mariadb-clients
-$ sudo pacman -S postgresql postgresql-libs
-```
+#### FreeBSD
-FreeBSD users will have to run the following:
+To install all run:
```bash
-# pkg install mysql56-client mysql56-server
-# pkg install postgresql94-client postgresql94-server
+# pkg install sqlite3
+ mysql80-client mysql80-server
+ postgresql11-client postgresql11-server
+ memcached imagemagick ffmpeg mupdf
+ yarn
+# portmaster databases/redis
```
-Or install them through ports (they are located under the `databases` folder).
-If you run into troubles during the installation of MySQL, please see
-[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html).
+Or install everything through ports (these packages are located under the
+`databases` folder).
-After that, run:
+NOTE: If you run into troubles during the installation of MySQL, please see
+[the MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/freebsd-installation.html).
-```bash
-$ rm .bundle/config
-$ bundle install
-```
+### Database Configuration
-First, we need to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file).
+There are couple of additional steps required to configure database engines
+required for running Active Record tests.
In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases:
@@ -247,13 +181,6 @@ mysql> GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.*
to 'rails'@'localhost';
```
-and create the test databases:
-
-```bash
-$ cd activerecord
-$ bundle exec rake db:mysql:build
-```
-
PostgreSQL's authentication works differently. To setup the development environment
with your development account, on Linux or BSD, you just have to run:
@@ -267,21 +194,24 @@ and for macOS:
$ createuser --superuser $USER
```
-Then, you need to create the test databases with:
+Then, you need to create the test databases for both MySQL and PostgreSQL with:
```bash
$ cd activerecord
-$ bundle exec rake db:postgresql:build
+$ bundle exec rake db:create
```
-It is possible to build databases for both PostgreSQL and MySQL with:
+NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
+
+You can also create test databases for each database engine separately:
```bash
$ cd activerecord
-$ bundle exec rake db:create
+$ bundle exec rake db:mysql:build
+$ bundle exec rake db:postgresql:build
```
-You can cleanup the databases using:
+and you can drop the databases using:
```bash
$ cd activerecord
@@ -290,138 +220,39 @@ $ bundle exec rake db:drop
NOTE: Using the Rake task to create the test databases ensures they have the correct character set and collation.
-NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
-
If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
-### Action Cable Setup
-
-Action Cable uses Redis as its default subscriptions adapter ([read more](action_cable_overview.html#broadcasting)). Thus, in order to have Action Cable's tests passing you need to install and have Redis running.
-
-#### Install Redis From Source
+### Install JavaScript dependencies
-Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](https://redis.io/download#installation).
-
-#### Install Redis From Package Manager
-
-On macOS, you can run:
-
-```bash
-$ brew install redis
-```
-
-Follow the instructions given by Homebrew to start these.
-
-On Ubuntu, just run:
-
-```bash
-$ sudo apt-get install redis-server
-```
-
-On Fedora or CentOS (requires EPEL enabled), just run:
-
-```bash
-$ sudo yum install redis
-```
-
-If you are running Arch Linux, just run:
-
-```bash
-$ sudo pacman -S redis
-$ sudo systemctl start redis
-```
-
-FreeBSD users will have to run the following:
-
-```bash
-# portmaster databases/redis
-```
-
-### Active Storage Setup
-
-When working on Active Storage, it is important to note that you need to
-install its JavaScript dependencies while working on that section of the
-codebase. In order to install these dependencies, it is necessary to
-have Yarn, a Node.js package manager, available on your system. A
-prerequisite for installing this package manager is that
-[Node.js](https://nodejs.org) is installed.
-
-
-On macOS, you can run:
-
-```bash
-$ brew install yarn
-```
-
-On Ubuntu, you can run:
-
-```bash
-$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
-$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
-
-$ sudo apt-get update && sudo apt-get install yarn
-```
-
-On Fedora or CentOS, just run:
-
-```bash
-$ sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
-
-$ sudo yum install yarn
-```
-
-Finally, after installing Yarn, you will need to run the following
-command inside of the `activestorage` directory to install the dependencies:
+If you installed Yarn, you will need to install the javascript dependencies:
```bash
$ yarn install
```
-Extracting previews, tested in Active Storage's test suite requires third-party
-applications, ImageMagick for images, FFmpeg for video and muPDF for PDFs, and on macOS also XQuartz
-and Poppler. Without these applications installed, Active Storage tests will
-raise errors.
+### Install Bundler gem
-On macOS you can run:
+Get a recent version of [Bundler](https://bundler.io/)
```bash
-$ brew install ffmpeg
-$ brew install imagemagick
-$ brew cask install xquartz
-$ brew install mupdf-tools
-$ brew install poppler
+$ gem install bundler
+$ gem update bundler
```
-On Ubuntu, you can run:
+and run:
```bash
-$ sudo apt-get update
-$ sudo apt-get install ffmpeg
-$ sudo apt-get install imagemagick
-$ sudo apt-get install mupdf mupdf-tools
+$ bundle install
```
-On Fedora or CentOS, just run:
+or:
```bash
-$ sudo yum install ffmpeg
-$ sudo yum install imagemagick
-$ sudo yum install mupdf
+$ bundle install --without db
```
-FreeBSD users can just run:
+if you don't need to run Active Record tests.
-```bash
-# pkg install imagemagick
-# pkg install ffmpeg
-# pkg install mupdf
-```
-
-On Arch Linux, you can run:
+### Contribute to Rails
-```bash
-$ sudo pacman -S ffmpeg
-$ sudo pacman -S imagemagick
-$ sudo pacman -S mupdf mupdf-tools
-$ sudo pacman -S poppler
-```
+After you've setup everything, read how you can start [contributing](contributing_to_ruby_on_rails.html#running-an-application-against-your-local-branch).
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 551179c523..25e4fdb4e6 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -74,7 +74,17 @@
-
name: Action Mailer Basics
url: action_mailer_basics.html
- description: This guide describes how to use Action Mailer to send and receive emails.
+ description: This guide describes how to use Action Mailer to send emails.
+ -
+ name: Action Mailbox Basics
+ work_in_progress: true
+ url: action_mailbox_basics.html
+ description: This guide describes how to use Action Mailbox to receive emails.
+ -
+ name: Action Text Overview
+ work_in_progress: true
+ url: action_text_overview.html
+ description: This guide describes how to use Action Text to handle rich text content.
-
name: Active Job Basics
url: active_job_basics.html
@@ -207,46 +217,46 @@
url: 6_0_release_notes.html
description: Release notes for Rails 6.0.
-
- name: 5.2 Release Notes
+ name: Version 5.2 - April 2018
url: 5_2_release_notes.html
description: Release notes for Rails 5.2.
-
- name: 5.1 Release Notes
+ name: Version 5.1 - April 2017
url: 5_1_release_notes.html
description: Release notes for Rails 5.1.
-
- name: 5.0 Release Notes
+ name: Version 5.0 - June 2016
url: 5_0_release_notes.html
description: Release notes for Rails 5.0.
-
- name: 4.2 Release Notes
+ name: Version 4.2 - December 2014
url: 4_2_release_notes.html
description: Release notes for Rails 4.2.
-
- name: 4.1 Release Notes
+ name: Version 4.1 - April 2014
url: 4_1_release_notes.html
description: Release notes for Rails 4.1.
-
- name: 4.0 Release Notes
+ name: Version 4.0 - June 2013
url: 4_0_release_notes.html
description: Release notes for Rails 4.0.
-
- name: 3.2 Release Notes
+ name: Version 3.2 - January 2012
url: 3_2_release_notes.html
description: Release notes for Rails 3.2.
-
- name: 3.1 Release Notes
+ name: Version 3.1 - August 2011
url: 3_1_release_notes.html
description: Release notes for Rails 3.1.
-
- name: 3.0 Release Notes
+ name: Version 3.0 - August 2010
url: 3_0_release_notes.html
description: Release notes for Rails 3.0.
-
- name: 2.3 Release Notes
+ name: Version 2.3 - March 2009
url: 2_3_release_notes.html
description: Release notes for Rails 2.3.
-
- name: 2.2 Release Notes
+ name: Version 2.2 - November 2008
url: 2_2_release_notes.html
description: Release notes for Rails 2.2.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 1e93a19c84..053e3aa16e 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1091,16 +1091,15 @@ main Rails application.
Engine model and controller classes can be extended by open classing them in the
main Rails application (since model and controller classes are just Ruby classes
that inherit Rails specific functionality). Open classing an Engine class
-redefines it for use in the main application. This is usually implemented by
-using the decorator pattern.
+redefines it for use in the main application.
For simple class modifications, use `Class#class_eval`. For complex class
modifications, consider using `ActiveSupport::Concern`.
-#### A note on Decorators and Loading Code
+#### A note on Overriding and Loading Code
-Because these decorators are not referenced by your Rails application itself,
-Rails' autoloading system will not kick in and load your decorators. This means
+Because these overrides are not referenced by your Rails application itself,
+Rails' autoloading system will not kick in and load your overrides. This means
that you need to require them yourself.
Here is some sample code to do this:
@@ -1112,7 +1111,7 @@ module Blorgh
isolate_namespace Blorgh
config.to_prepare do
- Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
+ Dir.glob(Rails.root + "app/overrides/**/*_override*.rb").each do |c|
require_dependency(c)
end
end
@@ -1120,15 +1119,15 @@ module Blorgh
end
```
-This doesn't apply to just Decorators, but anything that you add in an engine
+This doesn't apply to just overrides, but anything that you add in an engine
that isn't referenced by your main application.
-#### Implementing Decorator Pattern Using Class#class_eval
+#### Reopening existing classes using Class#class_eval
**Adding** `Article#time_since_created`:
```ruby
-# MyApp/app/decorators/models/blorgh/article_decorator.rb
+# MyApp/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
def time_since_created
@@ -1149,7 +1148,7 @@ end
**Overriding** `Article#summary`:
```ruby
-# MyApp/app/decorators/models/blorgh/article_decorator.rb
+# MyApp/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
def summary
@@ -1169,7 +1168,7 @@ class Article < ApplicationRecord
end
```
-#### Implementing Decorator Pattern Using ActiveSupport::Concern
+#### Reopening existing classes using ActiveSupport::Concern
Using `Class#class_eval` is great for simple adjustments, but for more complex
class modifications, you might want to consider using [`ActiveSupport::Concern`]
@@ -1498,6 +1497,8 @@ To hook into the initialization process of one of the following classes use the
| Class | Available Hooks |
| --------------------------------- | ------------------------------------ |
| `ActionCable` | `action_cable` |
+| `ActionCable::Channel::Base` | `action_cable_channel` |
+| `ActionCable::Connection::Base` | `action_cable_connection` |
| `ActionController::API` | `action_controller_api` |
| `ActionController::API` | `action_controller` |
| `ActionController::Base` | `action_controller_base` |
@@ -1505,13 +1506,19 @@ To hook into the initialization process of one of the following classes use the
| `ActionController::TestCase` | `action_controller_test_case` |
| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` |
| `ActionDispatch::SystemTestCase` | `action_dispatch_system_test_case` |
+| `ActionMailbox::Base` | `action_mailbox` |
+| `ActionMailbox::InboundEmail` | `action_mailbox_inbound_email` |
+| `ActionMailbox::TestCase` | `action_mailbox_test_case` |
| `ActionMailer::Base` | `action_mailer` |
| `ActionMailer::TestCase` | `action_mailer_test_case` |
+| `ActionText::Content` | `action_text_content` |
+| `ActionText::RichText` | `action_text_rich_text` |
| `ActionView::Base` | `action_view` |
| `ActionView::TestCase` | `action_view_test_case` |
| `ActiveJob::Base` | `active_job` |
| `ActiveJob::TestCase` | `active_job_test_case` |
| `ActiveRecord::Base` | `active_record` |
+| `ActiveStorage::Blob` | `active_storage_blob` |
| `ActiveSupport::TestCase` | `active_support_test_case` |
| `i18n` | `i18n` |
diff --git a/guides/source/generators.md b/guides/source/generators.md
index f028d14998..88ce4be8da 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -219,7 +219,7 @@ If we want to avoid generating the default `app/assets/stylesheets/scaffolds.scs
end
```
-The next customization on the workflow will be to stop generating stylesheet, JavaScript, and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following:
+The next customization on the workflow will be to stop generating stylesheet and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following:
```ruby
config.generators do |g|
@@ -227,7 +227,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
- g.javascripts false
end
```
@@ -285,7 +284,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
- g.javascripts false
g.helper :my_helper
end
```
@@ -350,7 +348,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
- g.javascripts false
end
```
@@ -385,7 +382,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :shoulda, fixture: false
g.stylesheets false
- g.javascripts false
# Add a fallback!
g.fallbacks[:shoulda] = :test_unit
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index e2f558d74c..7ee0d8c916 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -90,7 +90,7 @@ $ ruby -v
ruby 2.5.0
```
-Rails requires Ruby version 2.4.1 or later. If the version number returned is
+Rails requires Ruby version 2.5.0 or later. If the version number returned is
less than that number, you'll need to install a fresh copy of Ruby.
TIP: To quickly install Ruby and Ruby on Rails on your system in Windows, you can use
@@ -461,22 +461,19 @@ available, Rails will raise an exception.
Let's look at the full error message again:
->ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
+>ArticlesController#new is missing a template for request formats: text/html
-That's quite a lot of text! Let's quickly go through and understand what each
-part of it means.
+>NOTE!
+>Unless told otherwise, Rails expects an action to render a template with the same name, contained in a folder named after its controller. If this controller is an API responding with 204 (No Content), which does not require a template, then this error will occur when trying to access it via browser, since we expect an HTML template to be rendered for such requests. If that's the case, carry on.
-The first part identifies which template is missing. In this case, it's the
+The message identifies which template is missing. In this case, it's the
`articles/new` template. Rails will first look for this template. If not found,
-then it will attempt to load a template called `application/new`. It looks for
-one here because the `ArticlesController` inherits from `ApplicationController`.
+then it will attempt to load a template called `application/new`, because the
+`ArticlesController` inherits from `ApplicationController`.
-The next part of the message contains `request.formats` which specifies
-the format of template to be served in response. It is set to `text/html` as we
-requested this page via browser, so Rails is looking for an HTML template.
-`request.variant` specifies what kind of physical devices would be served by
-the response and helps Rails determine which template to use in the response.
-It is empty because no information has been provided.
+Next the message contains `request.formats` which specifies the format of
+template to be served in response. It is set to `text/html` as we requested
+this page via browser, so Rails is looking for an HTML template.
The simplest template that would work in this case would be one located at
`app/views/articles/new.html.erb`. The extension of this file name is important:
@@ -686,7 +683,7 @@ If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
(remember, yours will have a slightly different name), here's what you'll find:
```ruby
-class CreateArticles < ActiveRecord::Migration[5.0]
+class CreateArticles < ActiveRecord::Migration[6.0]
def change
create_table :articles do |t|
t.string :title
@@ -1558,7 +1555,7 @@ In addition to the model, Rails has also made a migration to create the
corresponding database table:
```ruby
-class CreateComments < ActiveRecord::Migration[5.0]
+class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.string :commenter
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index eba71eec60..d146685675 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -139,17 +139,20 @@ Note that appending directly to `I18n.load_paths` instead of to the application'
### Managing the Locale across Requests
-The default locale is used for all translations unless `I18n.locale` is explicitly set.
-
A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request.
-The locale can be set in a `before_action` in the `ApplicationController`:
+The default locale is used for all translations unless `I18n.locale=` or `I18n.with_locale` is used.
+
+`I18n.locale` can leak into subsequent requests served by the same thread/process if it is not consistently set in every controller. For example executing `I18n.locale = :es` in one POST requests will have effects for all later requests to controllers that don't set the locale, but only in that particular thread/process. For that reason, instead of `I18n.locale =` you can use `I18n.with_locale` which does not have this leak issue.
+
+The locale can be set in an `around_action` in the `ApplicationController`:
```ruby
-before_action :set_locale
+around_action :switch_locale
-def set_locale
- I18n.locale = params[:locale] || I18n.default_locale
+def switch_locale(&action)
+ locale = params[:locale] || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
```
@@ -169,10 +172,11 @@ One option you have is to set the locale from the domain name where your applica
You can implement it like this in your `ApplicationController`:
```ruby
-before_action :set_locale
+around_action :switch_locale
-def set_locale
- I18n.locale = extract_locale_from_tld || I18n.default_locale
+def switch_locale(&action)
+ locale = extract_locale_from_tld || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
# Get locale from top-level domain or return +nil+ if such locale is not available
@@ -212,7 +216,7 @@ This solution has aforementioned advantages, however, you may not be able or may
#### Setting the Locale from URL Params
-The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
+The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.with_locale(params[:locale], &action)` _around_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though.
@@ -275,8 +279,11 @@ NOTE: Have a look at various gems which simplify working with routes: [routing_f
An application with authenticated users may allow users to set a locale preference through the application's interface. With this approach, a user's selected locale preference is persisted in the database and used to set the locale for authenticated requests by that user.
```ruby
-def set_locale
- I18n.locale = current_user.try(:locale) || I18n.default_locale
+around_action :switch_locale
+
+def switch_locale(&action)
+ locale = current_user.try(:locale) || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
```
@@ -291,10 +298,11 @@ The `Accept-Language` HTTP header indicates the preferred language for request's
A trivial implementation of using an `Accept-Language` header would be:
```ruby
-def set_locale
+def switch_locale(&action)
logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
- I18n.locale = extract_locale_from_accept_language_header
- logger.debug "* Locale set to '#{I18n.locale}'"
+ locale = extract_locale_from_accept_language_header
+ logger.debug "* Locale set to '#{locale}'"
+ I18n.with_locale(locale, &action)
end
private
@@ -335,10 +343,12 @@ end
```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
- before_action :set_locale
- def set_locale
- I18n.locale = params[:locale] || I18n.default_locale
+ around_action :switch_locale
+
+ def switch_locale(&action)
+ locale = params[:locale] || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
end
```
@@ -664,6 +674,26 @@ I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
```
+If you want to perform interpolation on a bulk hash of translations, you need to pass `deep_interpolation: true` as a parameter. When you have the following dictionary:
+
+```yaml
+en:
+ welcome:
+ title: "Welcome!"
+ content: "Welcome to the %{app_name}"
+```
+
+then the nested interpolation will be ignored without the setting:
+
+```ruby
+I18n.t 'welcome', app_name: 'book store'
+# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}
+
+I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
+# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
+```
+
+
#### "Lazy" Lookup
Rails implements a convenient way to look up the locale inside _views_. When you have the following dictionary:
@@ -1105,7 +1135,7 @@ For several reasons the Simple backend shipped with Active Support only does the
That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs, by passing a backend instance to the `I18n.backend=` setter.
-For example, you can replace the Simple backend with the the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends.
+For example, you can replace the Simple backend with the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends.
With the Chain backend, you could use the Active Record backend and fall back to the (default) Simple backend:
diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb
index 76f01fea0a..10e388774c 100644
--- a/guides/source/index.html.erb
+++ b/guides/source/index.html.erb
@@ -1,6 +1,5 @@
-<% content_for :page_title do %>
-Ruby on Rails Guides
-<% end %>
+<% content_for :page_title, "Ruby on Rails Guides" %>
+<% content_for :description, "Ruby on Rails Guides" %>
<% content_for :header_section do %>
<%= render 'welcome' %>
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 1f42d72756..65a003fceb 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
+ <title><%= yield(:page_title) %></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">
@@ -14,6 +14,13 @@
<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>
+ <meta property="og:title" content="<%= yield(:page_title) %>" />
+ <meta name="description" content="<%= yield(:description) %>" />
+ <meta property="og:description" content="<%= yield(:description) %>" />
+ <meta property="og:locale" content="en_US" />
+ <meta property="og:site_name" content="Ruby on Rails Guides" />
+ <meta property="og:image" content="https://avatars.githubusercontent.com/u/4223" />
+ <meta property="og:type" content="website" />
</head>
<body class="guide">
<% if @edge %>
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index bc68a555c5..982df26987 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -195,6 +195,12 @@ You can also run commands as a super-user:
rails_command "log:clear", sudo: true
```
+You can also run commands that should abort application generation if they fail:
+
+```ruby
+rails_command "db:migrate", abort_on_failure: true
+```
+
### route(routing_code)
Adds a routing entry to the `config/routes.rb` file. In the steps above, we generated a person scaffold and also removed `README.rdoc`. Now, to make `PeopleController#index` the default page for the application:
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index c33851a0f9..69b5f254bf 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -35,7 +35,7 @@ application. Any Rack compliant web server should be using
### `rails server`
-`rails server` does the basic job of creating a `Rack::Server` object and starting the webserver.
+`rails server` does the basic job of creating a `Rack::Server` object and starting the web server.
Here's how `rails server` creates an instance of `Rack::Server`
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 84de727c11..92d5b45e7d 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -260,7 +260,7 @@ In each of these cases, the named routes remain the same as if you did not use `
| PATCH/PUT | /admin/articles/:id | articles#update | article_path(:id) |
| DELETE | /admin/articles/:id | articles#destroy | article_path(:id) |
-TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo' => '/foo#index'`._
+TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo', to: '/foo#index'`._
### Nested Resources
@@ -519,7 +519,7 @@ resources :photos do
end
```
-You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`.
+You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`. Route helpers will also be renamed from `preview_photo_url` and `preview_photo_path` to `photo_preview_url` and `photo_preview_path`.
#### Adding Collection Routes
@@ -543,6 +543,8 @@ resources :photos do
end
```
+NOTE: If you're defining additional resource routes with a symbol as the first positional argument, be mindful that it is not equivalent to using a string. Symbols infer controller actions while strings infer paths.
+
#### Adding Routes for Additional New Actions
To add an alternate new action using the `:on` shortcut:
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index f5c0ba5b2d..4b56cf6296 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -107,8 +107,8 @@ HTML Guides
-----------
Before generating the guides, make sure that you have the latest version of
-Bundler installed on your system. As of this writing, you must install Bundler
-1.3.5 or later on your device.
+Bundler installed on your system. You can find the latest Bundler version
+[here](https://rubygems.org/gems/bundler). As of this writing, it's v1.17.1.
To install the latest version of Bundler, run `gem install bundler`.
diff --git a/guides/source/security.md b/guides/source/security.md
index bb996cc39c..a2fb4663cf 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -32,27 +32,17 @@ In order to develop secure web applications you have to keep up to date on all l
Sessions
--------
-A good place to start looking at security is with sessions, which can be vulnerable to particular attacks.
+This chapter describes some particular attacks related to sessions, and security measures to protect your session data.
### What are Sessions?
-NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
+INFO: Sessions enable the application to maintain user-specific state, while users interact with the application. For example, sessions allow users to authenticate once and remain signed in for future requests.
-Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
-Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
+Most applications need to keep track of state for users that interact with the application. This could be the contents of a shopping basket, or the user id of the currently logged in user. This kind of user-specific state can be stored in the session.
-A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
+Rails provides a session object for each user that accesses the application. If the user already has an active session, Rails uses the existing session. Otherwise a new session is created.
-```ruby
-session[:user_id] = @current_user.id
-User.find(session[:user_id])
-```
-
-### Session ID
-
-NOTE: _The session ID is a 32-character random hex string._
-
-The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32 CryptoAPI) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
+NOTE: Read more about sessions and how to use them in [Action Controller Overview Guide](action_controller_overview.html#session).
### Session Hijacking
@@ -76,35 +66,31 @@ Hence, the cookie serves as temporary authentication for the web application. An
The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from 0.5%-10% of account balance, $0.5-$30 for credit card numbers ($20-$60 with full details), $0.1-$1.5 for identities (Name, SSN & DOB), $20-$50 for retailer accounts, and $6-$10 for cloud service provider accounts, according to the [Symantec Internet Security Threat Report (2017)](https://www.symantec.com/content/dam/symantec/docs/reports/istr-22-2017-en.pdf).
-### Session Guidelines
+### Session Storage
-Here are some general guidelines on sessions.
+NOTE: Rails uses `ActionDispatch::Session::CookieStore` as the default session storage.
-* _Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
-This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.
+TIP: Learn more about other session storages in [Action Controller Overview Guide](action_controller_overview.html#session).
-* _Critical data should not be stored in session_. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
-
-### Encrypted Session Storage
-
-NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._
-
-The `CookieStore` saves the session hash directly in a cookie on the
-client-side. The server retrieves the session hash from the cookie and
+Rails `CookieStore` saves the session hash in a cookie on the client-side.
+The server retrieves the session hash from the cookie and
eliminates the need for a session ID. That will greatly increase the
speed of the application, but it is a controversial storage option and
you have to think about the security implications and storage
limitations of it:
-* Cookies imply a strict size limit of 4kB. This is fine as you should
- not store large amounts of data in a session anyway, as described
- before. Storing the current user's database id in a session is common
- practice.
+* Cookies have a size limit of 4kB. Use cookies only for data which is relevant for the session.
+
+* Cookies are stored on the client-side. The client may preserve cookie contents even for expired cookies. The client may copy cookies to other machines. Avoid storing sensitive data in cookies.
+
+* Cookies are temporary by nature. The server can set expiration time for the cookie, but the client may delete the cookie and its contents before that. Persist all data that is of more permanent nature on the server side.
* Session cookies do not invalidate themselves and can be maliciously
reused. It may be a good idea to have your application invalidate old
session cookies using a stored timestamp.
+* Rails encrypts cookies by default. The client cannot read or edit the contents of the cookie, without breaking encryption. If you take appropriate care of your secrets, you can consider your cookies to be generally secured.
+
The `CookieStore` uses the
[encrypted](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-encrypted)
cookie jar to provide a secure, encrypted location to store session
@@ -114,32 +100,9 @@ verification key used for
[signed](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed)
cookies, is derived from the `secret_key_base` configuration value.
-As of Rails 5.2 encrypted cookies and sessions are protected using AES
-GCM encryption. This form of encryption is a type of Authenticated
-Encryption and couples authentication and encryption in single step
-while also producing shorter ciphertexts as compared to other
-algorithms previously used. The key for cookies encrypted with AES GCM
-are derived using a salt value defined by the
-`config.action_dispatch.authenticated_encrypted_cookie_salt`
-configuration value.
-
-Prior to this version, encrypted cookies were secured using AES in CBC
-mode with HMAC using SHA1 for authentication. The keys for this type of
-encryption and for HMAC verification were derived via the salts defined
-by `config.action_dispatch.encrypted_cookie_salt` and
-`config.action_dispatch.encrypted_signed_cookie_salt` respectively.
-
-Prior to Rails version 4 in both versions 2 and 3, session cookies were
-protected using only HMAC verification. As such, these session cookies
-only provided integrity to their content because the actual session data
-was stored in plaintext encoded as base64. This is how `signed` cookies
-work in the current version of Rails. These kinds of cookies are still
-useful for protecting the integrity of certain client-stored data and
-information.
-
-__Do not use a trivial secret for the `secret_key_base`, i.e. a word
-from a dictionary, or one which is shorter than 30 characters! Instead
-use `rails secret` to generate secret keys!__
+TIP: Secrets must be long and random. Use `rails secret` to get new unique secrets.
+
+INFO: Learn more about [managing credentials later in this guide](security.html#custom-credentials)
It is also important to use different salt values for encrypted and
signed cookies. Using the same value for different salt configuration
@@ -150,7 +113,7 @@ In test and development applications get a `secret_key_base` derived from the ap
secret_key_base: 492f...
-If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
+WARNING: If your application's secrets may have been exposed, strongly consider changing them. Changing `secret_key_base` will expire currently active sessions.
### Rotating Encrypted and Signed Cookies Configurations
@@ -1204,23 +1167,18 @@ loaded inline `<script>` elements.
Environmental Security
----------------------
-It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/secrets.yml`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
+It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, master key for `credentials.yml`, and other unencrypted secrets. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
### Custom credentials
-Rails generates a `config/credentials.yml.enc` to store third-party credentials
-within the repo. This is only viable because Rails encrypts the file with a master
-key that's generated into a version control ignored `config/master.key` — Rails
-will also look for that key in `ENV["RAILS_MASTER_KEY"]`. Rails also requires the
-key to boot in production, so the credentials can be read.
+Rails stores secrets in `config/credentials.yml.enc`, which is encrypted and hence cannot be edited directly. Rails uses `config/master.key` or alternatively looks for environment variable `ENV["RAILS_MASTER_KEY"]` to encrypt the credentials file. The credentials file can be stored in version control, as long as master key is kept safe.
-To edit stored credentials use `rails credentials:edit`.
+To add new secret to credentials, first run `rails secret` to get a new secret. Then run `rails credentials:edit` to edit credentials, and add the secret. Running `credentials:edit` creates new credentials file and master key, if they did not already exist.
By default, this file contains the application's
-`secret_key_base`, but it could also be used to store other credentials such as
-access keys for external APIs.
+`secret_key_base`, but it could also be used to store other credentials such as access keys for external APIs.
-The credentials added to this file are accessible via `Rails.application.credentials`.
+The secrets kept in credentials file are accessible via `Rails.application.credentials`.
For example, with the following decrypted `config/credentials.yml.enc`:
secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
@@ -1235,6 +1193,16 @@ version:
Rails.application.credentials.some_api_key! # => raises KeyError: :some_api_key is blank
```
+
+TIP: Learn more about credentials with `rails credentials:help`.
+
+WARNING: Keep your master key safe. Do not commit your master key.
+
+Dependency Management and CVEs
+------------------------------
+
+We don’t bump dependencies just to encourage use of new versions, including for security issues. This is because application owners need to manually update their gems regardless of our efforts. Use `bundle update --conservative gem_name` to safely update vulnerable dependencies.
+
Additional Resources
--------------------
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 8c21ccfba6..9667521f3b 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -33,11 +33,11 @@ Rails creates a `test` directory for you as soon as you create a Rails project u
```bash
$ ls -F test
-application_system_test_case.rb fixtures/ integration/ models/ test_helper.rb
-controllers/ helpers/ mailers/ system/
+application_system_test_case.rb controllers/ helpers/ mailers/ system/
+channels/ fixtures/ integration/ models/ test_helper.rb
```
-The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
+The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `channels` directory is meant to hold tests for Action Cable connection and channels. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
The system test directory holds system tests, which are used for full browser
testing of your application. System tests allow you to test your application
@@ -473,13 +473,12 @@ takes your entire test suite to run.
### Parallel testing with processes
The default parallelization method is to fork processes using Ruby's DRb system. The processes
-are forked based on the number of workers provided. The default is 2, but can be changed by the
-number passed to the parallelize method. Active Record automatically handles creating and
-migrating a new database for each worker to use.
+are forked based on the number of workers provided. The default number is the actual core count
+on the machine you are on, but can be changed by the number passed to the parallelize method.
To enable parallelization add the following to your `test_helper.rb`:
-```
+```ruby
class ActiveSupport::TestCase
parallelize(workers: 2)
end
@@ -489,35 +488,35 @@ The number of workers passed is the number of times the process will be forked.
parallelize your local test suite differently from your CI, so an environment variable is provided
to be able to easily change the number of workers a test run should use:
-```
+```bash
PARALLEL_WORKERS=15 rails test
```
-When parallelizing tests, Active Record automatically handles creating and migrating a database for each
+When parallelizing tests, Active Record automatically handles creating a database and loading the schema into the database for each
process. The databases will be suffixed with the number corresponding to the worker. For example, if you
have 2 workers the tests will create `test-database-0` and `test-database-1` respectively.
If the number of workers passed is 1 or fewer the processes will not be forked and the tests will not
be parallelized and the tests will use the original `test-database` database.
-Two hooks are provided, one runs when the process is forked, and one runs before the processes are closed.
+Two hooks are provided, one runs when the process is forked, and one runs before the forked process is closed.
These can be useful if your app uses multiple databases or perform other tasks that depend on the number of
workers.
The `parallelize_setup` method is called right after the processes are forked. The `parallelize_teardown` method
is called right before the processes are closed.
-```
+```ruby
class ActiveSupport::TestCase
parallelize_setup do |worker|
# setup databases
end
parallelize_teardown do |worker|
- # cleanup database
+ # cleanup databases
end
- parallelize(workers: 2)
+ parallelize(workers: :number_of_processors)
end
```
@@ -530,9 +529,9 @@ parallelizer is backed by Minitest's `Parallel::Executor`.
To change the parallelization method to use threads over forks put the following in your `test_helper.rb`
-```
+```ruby
class ActiveSupport::TestCase
- parallelize(workers: 2, with: :threads)
+ parallelize(workers: :number_of_processors, with: :threads)
end
```
@@ -542,7 +541,7 @@ The number of workers passed to `parallelize` determines the number of threads t
want to parallelize your local test suite differently from your CI, so an environment variable is provided
to be able to easily change the number of workers a test run should use:
-```
+```bash
PARALLEL_WORKERS=15 rails test
```
@@ -1398,6 +1397,56 @@ class ProfileControllerTest < ActionDispatch::IntegrationTest
end
```
+#### Using Separate Files
+
+If you find your helpers are cluttering `test_helper.rb`, you can extract them into separate files. One good place to store them is `lib/test`.
+
+```ruby
+# lib/test/multiple_assertions.rb
+module MultipleAssertions
+ def assert_multiple_of_fourty_two(number)
+ assert (number % 42 == 0), 'expected #{number} to be a multiple of 42'
+ end
+end
+```
+
+These helpers can then be explicitly required as needed and included as needed
+
+```ruby
+require 'test_helper'
+require 'test/multiple_assertions'
+
+class NumberTest < ActiveSupport::TestCase
+ include MultipleAssertions
+
+ test '420 is a multiple of fourty two' do
+ assert_multiple_of_fourty_two 420
+ end
+end
+```
+
+or they can continue to be included directly into the relevant parent classes
+
+```ruby
+# test/test_helper.rb
+require 'test/sign_in_helper'
+
+class ActionDispatch::IntegrationTest
+ include SignInHelper
+end
+```
+
+#### Eagerly Requiring Helpers
+
+You may find it convenient to eagerly require helpers in `test_helper.rb` so your test files have implicit access to them. This can be accomplished using globbing, as follows
+
+```ruby
+# test/test_helper.rb
+Dir[Rails.root.join('lib', 'test', '**', '*.rb')].each { |file| require file }
+```
+
+This has the downside of increasing the boot-up time, as opposed to manually requiring only the necessary files in your individual tests.
+
Testing Routes
--------------
@@ -1593,27 +1642,43 @@ NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in
If you want to have a clean slate outside these test cases, you can reset it
manually with: `ActionMailer::Base.deliveries.clear`
-### Functional Testing
+### Functional and System Testing
-Functional testing for mailers involves more than just checking that the email body, recipients, and so forth are correct. In functional mail tests you call the mail deliver methods and check that the appropriate emails have been appended to the delivery list. It is fairly safe to assume that the deliver methods themselves do their job. You are probably more interested in whether your own business logic is sending emails when you expect them to go out. For example, you can check that the invite friend operation is sending an email appropriately:
+Unit testing allows us to test the attributes of the email while functional and system testing allows us to test whether user interactions appropriately trigger the email to be delivered. For example, you can check that the invite friend operation is sending an email appropriately:
```ruby
+# Integration Test
require 'test_helper'
class UsersControllerTest < ActionDispatch::IntegrationTest
test "invite friend" do
- assert_difference 'ActionMailer::Base.deliveries.size', +1 do
+ # Asserts the difference in the ActionMailer::Base.deliveries
+ assert_emails 1 do
post invite_friend_url, params: { email: 'friend@example.com' }
end
- invite_email = ActionMailer::Base.deliveries.last
+ end
+end
+```
- assert_equal "You have been invited by me@example.com", invite_email.subject
- assert_equal 'friend@example.com', invite_email.to[0]
- assert_match(/Hi friend@example\.com/, invite_email.body.to_s)
+```ruby
+# System Test
+require 'test_helper'
+
+class UsersTest < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :headless_chrome
+
+ test "inviting a friend" do
+ visit invite_users_url
+ fill_in 'Email', with: 'friend@example.com'
+ assert_emails 1 do
+ click_on 'Invite'
+ end
end
end
```
+NOTE: The `assert_emails` method is not tied to a particular deliver method and will work with emails delivered with either the `deliver_now` or `deliver_later` method. If we explicitly want to assert that the email has been enqueued we can use the `assert_enqueued_emails` method. More information can be found in the [documentation here](https://api.rubyonrails.org/classes/ActionMailer/TestHelper.html).
+
Testing Jobs
------------
@@ -1666,6 +1731,139 @@ class ProductTest < ActiveJob::TestCase
end
```
+Testing Action Cable
+--------------------
+
+Since Action Cable is used at different levels inside your application,
+you'll need to test both the channels, connection classes themselves, and that other
+entities broadcast correct messages.
+
+### Connection Test Case
+
+By default, when you generate new Rails application with Action Cable, a test for the base connection class (`ApplicationCable::Connection`) is generated as well under `test/channels/application_cable` directory.
+
+Connection tests aim to check whether a connection's identifiers get assigned properly
+or that any improper connection requests are rejected. Here is an example:
+
+```ruby
+class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
+ test "connects with params" do
+ # Simulate a connection opening by calling the `connect` method
+ connect params: { user_id: 42 }
+
+ # You can access the Connection object via `connection` in tests
+ assert_equal connection.user_id, "42"
+ end
+
+ test "rejects connection without params" do
+ # Use `assert_reject_connection` matcher to verify that
+ # connection is rejected
+ assert_reject_connection { connect }
+ end
+end
+```
+
+You can also specify request cookies the same way you do in integration tests:
+
+```ruby
+test "connects with cookies" do
+ cookies.signed[:user_id] = "42"
+
+ connect
+
+ assert_equal connection.user_id, "42"
+end
+```
+
+See the API documentation for [`ActionCable::Connection::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Connection/TestCase.html) for more information.
+
+### Channel Test Case
+
+By default, when you generate a channel, an associated test will be generated as well
+under the `test/channels` directory. Here's an example test with a chat channel:
+
+```ruby
+require "test_helper"
+
+class ChatChannelTest < ActionCable::Channel::TestCase
+ test "subscribes and stream for room" do
+ # Simulate a subscription creation by calling `subscribe`
+ subscribe room: "15"
+
+ # You can access the Channel object via `subscription` in tests
+ assert subscription.confirmed?
+ assert_has_stream "chat_15"
+ end
+end
+```
+
+This test is pretty simple and only asserts that the channel subscribes the connection to a particular stream.
+
+You can also specify the underlying connection identifiers. Here's an example test with a web notifications channel:
+
+```ruby
+require "test_helper"
+
+class WebNotificationsChannelTest < ActionCable::Channel::TestCase
+ test "subscribes and stream for user" do
+ stub_connection current_user: users(:john)
+
+ subscribe
+
+ assert_has_stream_for users(:john)
+ end
+end
+```
+
+See the API documentation for [`ActionCable::Channel::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Channel/TestCase.html) for more information.
+
+### Custom Assertions And Testing Broadcasts Inside Other Components
+
+Action Cable ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActionCable::TestHelper`](http://api.rubyonrails.org/classes/ActionCable/TestHelper.html).
+
+It's a good practice to ensure that the correct message has been broadcasted inside other components (e.g. inside your controllers). This is precisely where
+the custom assertions provided by Action Cable are pretty useful. For instance,
+within a model:
+
+```ruby
+require 'test_helper'
+
+class ProductTest < ActionCable::TestCase
+ test "broadcast status after charge" do
+ assert_broadcast_on("products:#{product.id}", type: "charged") do
+ product.charge(account)
+ end
+ end
+end
+```
+
+If you want to test the broadcasting made with `Channel.broadcast_to`, you shoud use
+`Channel.broadcasting_for` to generate an underlying stream name:
+
+```ruby
+# app/jobs/chat_relay_job.rb
+class ChatRelayJob < ApplicationJob
+ def perform_later(room, message)
+ ChatChannel.broadcast_to room, text: message
+ end
+end
+
+# test/jobs/chat_relay_job_test.rb
+require 'test_helper'
+
+class ChatRelayJobTest < ActiveJob::TestCase
+ include ActionCable::TestHelper
+
+ test "broadcast message to room" do
+ room = rooms(:all)
+
+ assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
+ ChatRelayJob.perform_now(room, "Hi!")
+ end
+ end
+end
+```
+
Additional Testing Resources
----------------------------
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index a0553c1ccc..1f30ce1971 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -35,7 +35,7 @@ You can find a list of all released Rails versions [here](https://rubygems.org/g
Rails generally stays close to the latest released Ruby version when it's released:
-* Rails 6 requires Ruby 2.4.1 or newer.
+* Rails 6 requires Ruby 2.5.0 or newer.
* Rails 5 requires Ruby 2.2.2 or newer.
* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer.
* Rails 3.2.x is the last branch to support Ruby 1.8.7.
@@ -97,6 +97,42 @@ If you require your cookies to be read by 5.2 and older, or you are still valida
to allow you to rollback set
`Rails.application.config.action_dispatch.use_cookies_with_metadata` to `false`.
+### ActionCable javascript API Changes
+
+The ActionCable javascript package has been converted from CoffeeScript
+to ES2015, and we now publish the source code in the npm distribution.
+
+This change includes some breaking changes to optional parts of the
+ActionCable javascript API:
+
+- Configuration of the WebSocket adapter and logger adapter have been moved
+ from properties of `ActionCable` to properties of `ActionCable.adapters`.
+ If you are currently configuring these adapters you will need to make
+ these changes when upgrading:
+
+ ```diff
+ - ActionCable.WebSocket = MyWebSocket
+ + ActionCable.adapters.WebSocket = MyWebSocket
+ ```
+ ```diff
+ - ActionCable.logger = myLogger
+ + ActionCable.adapters.logger = myLogger
+ ```
+
+- The `ActionCable.startDebugging()` and `ActionCable.stopDebugging()`
+ methods have been removed and replaced with the property
+ `ActionCable.logger.enabled`. If you are currently using these methods you
+ will need to make these changes when upgrading:
+
+ ```diff
+ - ActionCable.startDebugging()
+ + ActionCable.logger.enabled = true
+ ```
+ ```diff
+ - ActionCable.stopDebugging()
+ + ActionCable.logger.enabled = false
+ ```
+
Upgrading from Rails 5.1 to Rails 5.2
-------------------------------------
@@ -407,7 +443,7 @@ want to add this feature it will need to be turned on in an initializer.
Rails 5 now supports per-form CSRF tokens to mitigate against code-injection attacks with forms
created by JavaScript. With this option turned on, forms in your application will each have their
-own CSRF token that is specified to the action and method for that form.
+own CSRF token that is specific to the action and method for that form.
config.action_controller.per_form_csrf_tokens = true