diff options
Diffstat (limited to 'guides')
123 files changed, 4325 insertions, 1591 deletions
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 2730d2dfea..518b6abfb3 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,2 +1,10 @@ +## Rails 5.2.0.beta2 (November 28, 2017) ## -Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/guides/CHANGELOG.md) for previous changes. +* No changes. + + +## Rails 5.2.0.beta1 (November 27, 2017) ## + +* No changes. + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/Rakefile b/guides/Rakefile index d2591f523c..84e18e0972 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -1,24 +1,37 @@ -namespace :guides do +# frozen_string_literal: true +namespace :guides do desc 'Generate guides (for authors), use ONLY=foo to process just "foo.md"' task generate: "generate:html" - namespace :generate do + # Guides are written in UTF-8, but the environment may be configured for some + # other locale, these tasks are responsible for ensuring the default external + # encoding is UTF-8. + # + # Real use cases: Generation was reported to fail on a machine configured with + # GBK (Chinese). The docs server once got misconfigured somehow and had "C", + # which broke generation too. + task :encoding do + %w(LANG LANGUAGE LC_ALL).each do |env_var| + ENV[env_var] = "en_US.UTF-8" + end + end + namespace :generate do desc "Generate HTML guides" - task :html do + task html: :encoding do ENV["WARNINGS"] = "1" # authors can't disable this ruby "rails_guides.rb" end desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211" - task :kindle do + task kindle: :encoding do require "kindlerb" unless Kindlerb.kindlegen_available? abort "Please run `setupkindlerb` to install kindlegen" end unless `convert` =~ /convert/ - abort "Please install ImageMagick`" + abort "Please install ImageMagick" end ENV["KINDLE"] = "1" Rake::Task["guides:generate:html"].invoke @@ -27,13 +40,13 @@ namespace :guides do # Validate guides ------------------------------------------------------------------------- desc 'Validate guides, use ONLY=foo to process just "foo.html"' - task :validate do + task validate: :encoding do ruby "w3c_validator.rb" end desc "Show help" task :help do - puts <<-help + puts <<HELP Guides are taken from the source directory, and the result goes into the output directory. Assets are stored under files, and copied to output/files as @@ -46,8 +59,9 @@ All of these processes are handled via rake tasks, here's a full list of them: #{%x[rake -T]} Some arguments may be passed via environment variables: - WARNINGS=1 - Internal links (anchors) are checked, also detects duplicated IDs. + RAILS_VERSION=tag + If guides are being generated for a specific Rails version set the Git tag + here, otherwise the current SHA1 is going to be used to generate edge guides. ALL=1 Force generation of all guides. @@ -65,15 +79,12 @@ Some arguments may be passed via environment variables: Use it when you want to generate translated guides in source/<GUIDES_LANGUAGE> folder (such as source/es) - EDGE=1 - Indicate generated guides should be marked as edge. - Examples: - $ rake guides:generate ALL=1 - $ rake guides:generate EDGE=1 - $ rake guides:generate:kindle EDGE=1 + $ rake guides:generate ALL=1 RAILS_VERSION=v5.1.0 + $ rake guides:generate ONLY=migrations + $ rake guides:generate:kindle $ rake guides:generate GUIDES_LANGUAGE=es - help +HELP end end diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png Binary files differindex 077d237e4e..2b8c1d52ea 100644 --- a/guides/assets/images/belongs_to.png +++ b/guides/assets/images/belongs_to.png diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png Binary files differindex c489e4c00e..3f16f3b280 100644 --- a/guides/assets/images/getting_started/article_with_comments.png +++ b/guides/assets/images/getting_started/article_with_comments.png diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png Binary files differindex 5b88a842b2..d05ef31bbe 100644 --- a/guides/assets/images/getting_started/challenge.png +++ b/guides/assets/images/getting_started/challenge.png diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png Binary files differindex 9755f581a6..ce65734e6c 100644 --- a/guides/assets/images/getting_started/confirm_dialog.png +++ b/guides/assets/images/getting_started/confirm_dialog.png diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png Binary files differindex 9f32c68472..50b178808e 100644 --- a/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png +++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png Binary files differindex 98bff37d4a..6eefd2885a 100644 --- a/guides/assets/images/getting_started/form_with_errors.png +++ b/guides/assets/images/getting_started/form_with_errors.png diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png Binary files differindex 0566a3ffde..a2a087a598 100644 --- a/guides/assets/images/getting_started/index_action_with_edit_link.png +++ b/guides/assets/images/getting_started/index_action_with_edit_link.png diff --git a/guides/assets/images/getting_started/new_article.png b/guides/assets/images/getting_started/new_article.png Binary files differindex bd3ae4fa67..6edcc161b6 100644 --- a/guides/assets/images/getting_started/new_article.png +++ b/guides/assets/images/getting_started/new_article.png diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png Binary files differindex baccb11322..44f89ec8de 100644 --- a/guides/assets/images/getting_started/rails_welcome.png +++ b/guides/assets/images/getting_started/rails_welcome.png diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png Binary files differindex ed62862291..52150f0426 100644 --- a/guides/assets/images/getting_started/routing_error_no_controller.png +++ b/guides/assets/images/getting_started/routing_error_no_controller.png diff --git a/guides/assets/images/getting_started/show_action_for_articles.png b/guides/assets/images/getting_started/show_action_for_articles.png Binary files differindex 4dad704f89..68837131f7 100644 --- a/guides/assets/images/getting_started/show_action_for_articles.png +++ b/guides/assets/images/getting_started/show_action_for_articles.png 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 Binary files differindex f4f054f3c6..a1603f5d28 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 diff --git a/guides/assets/images/getting_started/unknown_action_create_for_articles.png b/guides/assets/images/getting_started/unknown_action_create_for_articles.png Binary files differindex fd20cd53dc..ec4758e085 100644 --- a/guides/assets/images/getting_started/unknown_action_create_for_articles.png +++ b/guides/assets/images/getting_started/unknown_action_create_for_articles.png diff --git a/guides/assets/images/getting_started/unknown_action_new_for_articles.png b/guides/assets/images/getting_started/unknown_action_new_for_articles.png Binary files differindex e948a51e4a..f7e7464d61 100644 --- a/guides/assets/images/getting_started/unknown_action_new_for_articles.png +++ b/guides/assets/images/getting_started/unknown_action_new_for_articles.png diff --git a/guides/assets/images/habtm.png b/guides/assets/images/habtm.png Binary files differindex b062bc73fe..7e508cc1a6 100644 --- a/guides/assets/images/habtm.png +++ b/guides/assets/images/habtm.png diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png Binary files differindex 79da2613d7..36ccf9f0f6 100644 --- a/guides/assets/images/has_many.png +++ b/guides/assets/images/has_many.png diff --git a/guides/assets/images/has_many_through.png b/guides/assets/images/has_many_through.png Binary files differindex 858c898dc1..9e9caabd73 100644 --- a/guides/assets/images/has_many_through.png +++ b/guides/assets/images/has_many_through.png diff --git a/guides/assets/images/has_one.png b/guides/assets/images/has_one.png Binary files differindex 93faa05b07..c29c6b9c59 100644 --- a/guides/assets/images/has_one.png +++ b/guides/assets/images/has_one.png diff --git a/guides/assets/images/has_one_through.png b/guides/assets/images/has_one_through.png Binary files differindex 07dac1a27d..fdf13286c4 100644 --- a/guides/assets/images/has_one_through.png +++ b/guides/assets/images/has_one_through.png diff --git a/guides/assets/images/header_backdrop.png b/guides/assets/images/header_backdrop.png Binary files differindex 72b030478f..81f4d91774 100644 --- a/guides/assets/images/header_backdrop.png +++ b/guides/assets/images/header_backdrop.png diff --git a/guides/assets/images/i18n/demo_html_safe.png b/guides/assets/images/i18n/demo_html_safe.png Binary files differindex 9afa8ebec1..be75d4830e 100644 --- a/guides/assets/images/i18n/demo_html_safe.png +++ b/guides/assets/images/i18n/demo_html_safe.png diff --git a/guides/assets/images/i18n/demo_localized_pirate.png b/guides/assets/images/i18n/demo_localized_pirate.png Binary files differindex bf8d0b558c..528cc27900 100644 --- a/guides/assets/images/i18n/demo_localized_pirate.png +++ b/guides/assets/images/i18n/demo_localized_pirate.png diff --git a/guides/assets/images/i18n/demo_translated_en.png b/guides/assets/images/i18n/demo_translated_en.png Binary files differindex e887bfa306..bbb5e93c3a 100644 --- a/guides/assets/images/i18n/demo_translated_en.png +++ b/guides/assets/images/i18n/demo_translated_en.png diff --git a/guides/assets/images/i18n/demo_translated_pirate.png b/guides/assets/images/i18n/demo_translated_pirate.png Binary files differindex aa5618a865..305fa93a14 100644 --- a/guides/assets/images/i18n/demo_translated_pirate.png +++ b/guides/assets/images/i18n/demo_translated_pirate.png diff --git a/guides/assets/images/i18n/demo_translation_missing.png b/guides/assets/images/i18n/demo_translation_missing.png Binary files differindex 867aa7c42d..e9833ba307 100644 --- a/guides/assets/images/i18n/demo_translation_missing.png +++ b/guides/assets/images/i18n/demo_translation_missing.png diff --git a/guides/assets/images/i18n/demo_untranslated.png b/guides/assets/images/i18n/demo_untranslated.png Binary files differindex 2ea6404822..2653abc491 100644 --- a/guides/assets/images/i18n/demo_untranslated.png +++ b/guides/assets/images/i18n/demo_untranslated.png diff --git a/guides/assets/images/icons/callouts/14.png b/guides/assets/images/icons/callouts/14.png Binary files differindex 4274e6580a..dbde9ca749 100644 --- a/guides/assets/images/icons/callouts/14.png +++ b/guides/assets/images/icons/callouts/14.png diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png Binary files differindex de23c0aa87..a0e855befa 100644 --- a/guides/assets/images/icons/example.png +++ b/guides/assets/images/icons/example.png diff --git a/guides/assets/images/icons/home.png b/guides/assets/images/icons/home.png Binary files differindex 24149d6e78..e70e164522 100644 --- a/guides/assets/images/icons/home.png +++ b/guides/assets/images/icons/home.png diff --git a/guides/assets/images/icons/important.png b/guides/assets/images/icons/important.png Binary files differindex dafcf0f59e..bab53bf3aa 100644 --- a/guides/assets/images/icons/important.png +++ b/guides/assets/images/icons/important.png diff --git a/guides/assets/images/icons/next.png b/guides/assets/images/icons/next.png Binary files differindex 355b329f5a..a158832725 100644 --- a/guides/assets/images/icons/next.png +++ b/guides/assets/images/icons/next.png diff --git a/guides/assets/images/icons/note.png b/guides/assets/images/icons/note.png Binary files differindex 08d35a6f5c..62eec7845f 100644 --- a/guides/assets/images/icons/note.png +++ b/guides/assets/images/icons/note.png diff --git a/guides/assets/images/icons/prev.png b/guides/assets/images/icons/prev.png Binary files differindex ea564c865e..8a96960422 100644 --- a/guides/assets/images/icons/prev.png +++ b/guides/assets/images/icons/prev.png diff --git a/guides/assets/images/icons/tip.png b/guides/assets/images/icons/tip.png Binary files differindex d834e6d1bb..a5316d318f 100644 --- a/guides/assets/images/icons/tip.png +++ b/guides/assets/images/icons/tip.png diff --git a/guides/assets/images/icons/up.png b/guides/assets/images/icons/up.png Binary files differindex 379f0045af..6cac818170 100644 --- a/guides/assets/images/icons/up.png +++ b/guides/assets/images/icons/up.png diff --git a/guides/assets/images/polymorphic.png b/guides/assets/images/polymorphic.png Binary files differindex a3cbc4502a..d630db9e01 100644 --- a/guides/assets/images/polymorphic.png +++ b/guides/assets/images/polymorphic.png diff --git a/guides/assets/images/rails4_features.png b/guides/assets/images/rails4_features.png Binary files differindex b3bd5ef69e..ac73f05cf7 100644 --- a/guides/assets/images/rails4_features.png +++ b/guides/assets/images/rails4_features.png diff --git a/guides/assets/images/session_fixation.png b/guides/assets/images/session_fixation.png Binary files differindex ac3ab01614..e009484f09 100644 --- a/guides/assets/images/session_fixation.png +++ b/guides/assets/images/session_fixation.png diff --git a/guides/assets/images/tab_yellow.png b/guides/assets/images/tab_yellow.png Binary files differindex 3ab1c56c4d..053c807d28 100644 --- a/guides/assets/images/tab_yellow.png +++ b/guides/assets/images/tab_yellow.png diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index b56699a0d0..b27776745a 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -294,6 +294,10 @@ a, a:link, a:visited { #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 --------------------------------------- */ diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 85d698f81b..557b1d7bef 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,17 +9,19 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + # Activate the gem you are reporting the issue against. - gem "rails", "5.0.1" + gem "rails", "5.1.0" end require "rack/test" require "action_controller/railtie" class TestApp < Rails::Application - config.root = File.dirname(__FILE__) + config.root = __dir__ config.session_store :cookie_store, key: "cookie_store_key" - secrets.secret_token = "secret_token" secrets.secret_key_base = "secret_key_base" config.logger = Logger.new($stdout) diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 7644f6fe4a..ffd81c0079 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,15 +9,16 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" end require "action_controller/railtie" class TestApp < Rails::Application - config.root = File.dirname(__FILE__) - secrets.secret_token = "secret_token" + config.root = __dir__ 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 f715caec95..013d1f8602 100644 --- a/guides/bug_report_templates/active_job_gem.rb +++ b/guides/bug_report_templates/active_job_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,11 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + # Activate the gem you are reporting the issue against. - gem "activejob", "5.0.1" + gem "activejob", "5.1.0" end require "minitest/autorun" diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb index 7591470440..4bcee07607 100644 --- a/guides/bug_report_templates/active_job_master.rb +++ b/guides/bug_report_templates/active_job_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,10 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" end require "active_job" diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 98edcb76b1..921917fbe9 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,11 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + # Activate the gem you are reporting the issue against. - gem "activerecord", "5.0.1" + gem "activerecord", "5.1.0" gem "sqlite3" end diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 8bbc1ef19e..914f04f51a 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,10 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" gem "sqlite3" end diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb index ece6ae4f12..9002b8f428 100644 --- a/guides/bug_report_templates/active_record_migrations_gem.rb +++ b/guides/bug_report_templates/active_record_migrations_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,11 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + # Activate the gem you are reporting the issue against. - gem "activerecord", "5.0.1" + gem "activerecord", "5.1.0" gem "sqlite3" end @@ -32,7 +37,7 @@ end class Payment < ActiveRecord::Base end -class ChangeAmountToAddScale < ActiveRecord::Migration[5.0] +class ChangeAmountToAddScale < ActiveRecord::Migration[5.1] def change reversible do |dir| dir.up do @@ -48,16 +53,14 @@ end class BugTest < Minitest::Test def test_migration_up - migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale]) - migrator.run + ChangeAmountToAddScale.migrate(:up) Payment.reset_column_information assert_equal "decimal(10,2)", Payment.columns.last.sql_type end def test_migration_down - migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale]) - migrator.run + ChangeAmountToAddScale.migrate(:down) Payment.reset_column_information assert_equal "decimal(10,0)", Payment.columns.last.sql_type diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb index 84a4b71909..194b093ac3 100644 --- a/guides/bug_report_templates/active_record_migrations_master.rb +++ b/guides/bug_report_templates/active_record_migrations_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,10 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" gem "sqlite3" end @@ -32,7 +36,7 @@ end class Payment < ActiveRecord::Base end -class ChangeAmountToAddScale < ActiveRecord::Migration[5.0] +class ChangeAmountToAddScale < ActiveRecord::Migration[5.2] def change reversible do |dir| dir.up do @@ -48,16 +52,14 @@ end class BugTest < Minitest::Test def test_migration_up - migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale]) - migrator.run + ChangeAmountToAddScale.migrate(:up) Payment.reset_column_information assert_equal "decimal(10,2)", Payment.columns.last.sql_type end def test_migration_down - migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale]) - migrator.run + ChangeAmountToAddScale.migrate(:down) Payment.reset_column_information assert_equal "decimal(10,0)", Payment.columns.last.sql_type diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb index a0b541d012..046572148b 100644 --- a/guides/bug_report_templates/benchmark.rb +++ b/guides/bug_report_templates/benchmark.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,10 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" gem "benchmark-ips" end diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb index fa9f94eea0..60e8322c2a 100644 --- a/guides/bug_report_templates/generic_gem.rb +++ b/guides/bug_report_templates/generic_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,11 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + # Activate the gem you are reporting the issue against. - gem "activesupport", "5.0.1" + gem "activesupport", "5.1.0" end require "active_support/core_ext/object/blank" diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index ed45726e92..727f428960 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e @@ -7,8 +9,10 @@ end gemfile(true) do source "https://rubygems.org" + + git_source(:github) { |repo| "https://github.com/#{repo}.git" } + gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" end require "active_support" diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index 557d23f78c..f2d4d6f647 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -1,17 +1,29 @@ -pwd = File.dirname(__FILE__) -$:.unshift pwd +# frozen_string_literal: true -begin - # Guides generation in the Rails repo. - as_lib = File.join(pwd, "../activesupport/lib") - ap_lib = File.join(pwd, "../actionpack/lib") +$:.unshift __dir__ - $:.unshift as_lib if File.directory?(as_lib) - $:.unshift ap_lib if File.directory?(ap_lib) -rescue LoadError - # Guides generation from gems. - gem "actionpack", ">= 3.0" -end +as_lib = File.expand_path("../activesupport/lib", __dir__) +ap_lib = File.expand_path("../actionpack/lib", __dir__) +av_lib = File.expand_path("../actionview/lib", __dir__) + +$:.unshift as_lib if File.directory?(as_lib) +$:.unshift ap_lib if File.directory?(ap_lib) +$:.unshift av_lib if File.directory?(av_lib) require "rails_guides/generator" -RailsGuides::Generator.new.generate +require "active_support/core_ext/object/blank" + +env_value = ->(name) { ENV[name].presence } +env_flag = ->(name) { "1" == env_value[name] } + +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"] +).generate diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index a818ca9d72..7205f37be7 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -1,53 +1,4 @@ -# --------------------------------------------------------------------------- -# -# This script generates the guides. It can be invoked via the -# guides:generate rake task within the guides directory. -# -# Guides are taken from the source directory, and the resulting HTML goes into the -# output directory. Assets are stored under files, and copied to output/files as -# part of the generation process. -# -# Some arguments may be passed via environment variables: -# -# WARNINGS -# If you are writing a guide, please work always with WARNINGS=1. Users can -# generate the guides, and thus this flag is off by default. -# -# Internal links (anchors) are checked. If a reference is broken levenshtein -# distance is used to suggest an existing one. This is useful since IDs are -# generated by Markdown from headers and thus edits alter them. -# -# Also detects duplicated IDs. They happen if there are headers with the same -# text. Please do resolve them, if any, so guides are valid XHTML. -# -# ALL -# Set to "1" to force the generation of all guides. -# -# ONLY -# Use ONLY if you want to generate only one or a set of guides. Prefixes are -# enough: -# -# # generates only association_basics.html -# ONLY=assoc rake guides:generate -# -# Separate many using commas: -# -# # generates only association_basics.html and command_line.html -# ONLY=assoc,command rake guides:generate -# -# Note that if you are working on a guide generation will by default process -# only that one, so ONLY is rarely used nowadays. -# -# GUIDES_LANGUAGE -# Use GUIDES_LANGUAGE when you want to generate translated guides in -# <tt>source/<GUIDES_LANGUAGE></tt> folder (such as <tt>source/es</tt>). -# Ignore it when generating English guides. -# -# EDGE -# Set to "1" to indicate generated guides should be marked as edge. This -# inserts a badge and changes the preamble of the home page. -# -# --------------------------------------------------------------------------- +# frozen_string_literal: true require "set" require "fileutils" @@ -64,46 +15,37 @@ require "rails_guides/levenshtein" module RailsGuides class Generator - attr_reader :guides_dir, :source_dir, :output_dir, :edge, :warnings, :all - GUIDES_RE = /\.(?:erb|md)\z/ - def initialize(output = nil) - set_flags_from_environment + def initialize(edge:, version:, all:, only:, kindle:, language:) + @edge = edge + @version = version + @all = all + @only = only + @kindle = kindle + @language = language - if kindle? + if @kindle check_for_kindlegen register_kindle_mime_types end - initialize_dirs(output) + initialize_dirs create_output_dir_if_needed - end - - def set_flags_from_environment - @edge = ENV["EDGE"] == "1" - @warnings = ENV["WARNINGS"] == "1" - @all = ENV["ALL"] == "1" - @kindle = ENV["KINDLE"] == "1" - @version = ENV["RAILS_VERSION"] || "local" - @lang = ENV["GUIDES_LANGUAGE"] - end - - def register_kindle_mime_types - Mime::Type.register_alias("application/xml", :opf, %w(opf)) - Mime::Type.register_alias("application/xml", :ncx, %w(ncx)) + initialize_markdown_renderer end def generate generate_guides copy_assets - generate_mobi if kindle? + generate_mobi if @kindle end private - def kindle? - @kindle + def register_kindle_mime_types + Mime::Type.register_alias("application/xml", :opf, %w(opf)) + Mime::Type.register_alias("application/xml", :ncx, %w(ncx)) end def check_for_kindlegen @@ -114,29 +56,35 @@ module RailsGuides def generate_mobi require "rails_guides/kindle" - out = "#{output_dir}/kindlegen.out" - Kindle.generate(output_dir, mobi, out) + out = "#{@output_dir}/kindlegen.out" + Kindle.generate(@output_dir, mobi, out) puts "(kindlegen log at #{out})." end def mobi - "ruby_on_rails_guides_#@version%s.mobi" % (@lang.present? ? ".#@lang" : "") + mobi = "ruby_on_rails_guides_#{@version || @edge[0, 7]}" + mobi += ".#{@language}" if @language + mobi += ".mobi" end - def initialize_dirs(output) - @guides_dir = File.join(File.dirname(__FILE__), "..") - @source_dir = "#@guides_dir/source/#@lang" - @output_dir = if output - output - elsif kindle? - "#@guides_dir/output/kindle/#@lang" - else - "#@guides_dir/output/#@lang" - end.sub(%r</$>, "") + def initialize_dirs + @guides_dir = File.expand_path("..", __dir__) + + @source_dir = "#{@guides_dir}/source" + @source_dir += "/#{@language}" if @language + + @output_dir = "#{@guides_dir}/output" + @output_dir += "/kindle" if @kindle + @output_dir += "/#{@language}" if @language end def create_output_dir_if_needed - FileUtils.mkdir_p(output_dir) + FileUtils.mkdir_p(@output_dir) + end + + def initialize_markdown_renderer + Markdown::Renderer.edge = @edge + Markdown::Renderer.version = @version end def generate_guides @@ -147,27 +95,27 @@ module RailsGuides end def guides_to_generate - guides = Dir.entries(source_dir).grep(GUIDES_RE) + guides = Dir.entries(@source_dir).grep(GUIDES_RE) - if kindle? - Dir.entries("#{source_dir}/kindle").grep(GUIDES_RE).map do |entry| + if @kindle + Dir.entries("#{@source_dir}/kindle").grep(GUIDES_RE).map do |entry| next if entry == "KINDLE.md" guides << "kindle/#{entry}" end end - ENV.key?("ONLY") ? select_only(guides) : guides + @only ? select_only(guides) : guides end def select_only(guides) - prefixes = ENV["ONLY"].split(",").map(&:strip) + prefixes = @only.split(",").map(&:strip) guides.select do |guide| - guide.start_with?("kindle".freeze, *prefixes) + guide.start_with?("kindle", *prefixes) end end def copy_assets - FileUtils.cp_r(Dir.glob("#{guides_dir}/assets/*"), output_dir) + FileUtils.cp_r(Dir.glob("#{@guides_dir}/assets/*"), @output_dir) end def output_file_for(guide) @@ -179,22 +127,28 @@ module RailsGuides end def output_path_for(output_file) - File.join(output_dir, File.basename(output_file)) + File.join(@output_dir, File.basename(output_file)) end def generate?(source_file, output_file) - fin = File.join(source_dir, source_file) + fin = File.join(@source_dir, source_file) fout = output_path_for(output_file) - all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin) + @all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin) end def generate_guide(guide, output_file) output_path = output_path_for(output_file) puts "Generating #{guide} as #{output_file}" - layout = kindle? ? "kindle/layout" : "layout" + layout = @kindle ? "kindle/layout" : "layout" File.open(output_path, "w") do |f| - view = ActionView::Base.new(source_dir, edge: @edge, version: @version, mobi: "kindle/#{mobi}", lang: @lang) + view = ActionView::Base.new( + @source_dir, + edge: @edge, + version: @version, + mobi: "kindle/#{mobi}", + language: @language + ) view.extend(Helpers) if guide =~ /\.(\w+)\.erb$/ @@ -202,10 +156,15 @@ module RailsGuides # Passing a template handler in the template name is deprecated. So pass the file name without the extension. result = view.render(layout: layout, formats: [$1], file: $`) else - body = File.read(File.join(source_dir, guide)) - result = RailsGuides::Markdown.new(view, layout).render(body) - - warn_about_broken_links(result) if @warnings + body = File.read("#{@source_dir}/#{guide}") + result = RailsGuides::Markdown.new( + view: view, + layout: layout, + edge: @edge, + version: @version + ).render(body) + + warn_about_broken_links(result) end f.write(result) @@ -231,7 +190,7 @@ module RailsGuides # Footnotes. anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten) anchors += Set.new(html.scan(/<sup\s+class="footnote"\s+id="([^"]+)/).flatten) - return anchors + anchors end def check_fragment_identifiers(html, anchors) diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index 6f4b0b492c..a6970fb90c 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "yaml" module RailsGuides @@ -15,7 +17,7 @@ module RailsGuides end def documents_by_section - @documents_by_section ||= YAML.load_file(File.expand_path("../../source/#{@lang ? @lang + '/' : ''}documents.yaml", __FILE__)) + @documents_by_section ||= YAML.load_file(File.expand_path("../source/#{@language ? @language + '/' : ''}documents.yaml", __dir__)) end def documents_flat diff --git a/guides/rails_guides/indexer.rb b/guides/rails_guides/indexer.rb index c58b6b85a2..c707464cdf 100644 --- a/guides/rails_guides/indexer.rb +++ b/guides/rails_guides/indexer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/blank" require "active_support/core_ext/string/inflections" diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 9536d0bd3b..87a369a15a 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require "kindlerb" require "nokogiri" diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb index 40c6a5c372..c48af797fa 100644 --- a/guides/rails_guides/levenshtein.rb +++ b/guides/rails_guides/levenshtein.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RailsGuides module Levenshtein # This code is based directly on the Text gem implementation. @@ -36,7 +38,7 @@ module RailsGuides d[m] = x end - return x + x end end end diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 009e5aff99..84f95eec68 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -1,15 +1,19 @@ +# frozen_string_literal: true + require "redcarpet" require "nokogiri" require "rails_guides/markdown/renderer" module RailsGuides class Markdown - def initialize(view, layout) - @view = view - @layout = layout + def initialize(view:, layout:, edge:, version:) + @view = view + @layout = layout + @edge = edge + @version = version @index_counter = Hash.new(0) - @raw_header = "" - @node_ids = {} + @raw_header = "" + @node_ids = {} end def render(body) @@ -60,7 +64,8 @@ module RailsGuides autolink: true, strikethrough: true, superscript: true, - tables: true) + tables: true + ) end def extract_raw_header_and_body @@ -102,6 +107,10 @@ module RailsGuides node.inner_html = "#{node_index(hierarchy)} #{node.inner_html}" end end + + doc.css("h3, h4, h5, h6").each do |node| + node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>" + end end.to_html end end diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index deab741023..78820a7856 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -1,9 +1,9 @@ +# frozen_string_literal: true + module RailsGuides class Markdown class Renderer < Redcarpet::Render::HTML - def initialize(options = {}) - super - end + cattr_accessor :edge, :version def block_code(code, language) <<-HTML @@ -15,6 +15,16 @@ module RailsGuides HTML end + def link(url, title, content) + if url.start_with?("http://api.rubyonrails.org") + %(<a href="#{api_link(url)}">#{content}</a>) + elsif title + %(<a href="#{url}" title="#{title}">#{content}</a>) + else + %(<a href="#{url}">#{content}</a>) + end + end + def header(text, header_level) # Always increase the heading level by 1, so we can use h1, h2 heading in the document header_level += 1 @@ -23,7 +33,9 @@ HTML end def paragraph(text) - if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/ + if text =~ %r{^NOTE:\s+Defined\s+in\s+<code>(.*?)</code>\.?$} + %(<div class="note"><p>Defined in <code><a href="#{github_file_url($1)}">#{$1}</a></code>.</p></div>) + elsif text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/ convert_notes(text) elsif text.include?("DO NOT READ THIS FILE ON GITHUB") elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/ @@ -63,7 +75,7 @@ HTML # # It is important that we do not eat more than one newline # because formatting may be wrong otherwise. For example, - # if a bulleted list follows the first item is not rendered + # if a bulleted list follows, the first item is not rendered # as a list item, but as a paragraph starting with a plain # asterisk. body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do @@ -79,6 +91,33 @@ HTML %(<div class="#{css_class}"><p>#{$2.strip}</p></div>) end end + + def github_file_url(file_path) + tree = version || edge + + root = file_path[%r{(\w+)/}, 1] + path = \ + case root + when "abstract_controller", "action_controller", "action_dispatch" + "actionpack/lib/#{file_path}" + when /\A(action|active)_/ + "#{root.sub("_", "")}/lib/#{file_path}" + else + file_path + end + + "https://github.com/rails/rails/tree/#{tree}/#{path}" + end + + def api_link(url) + if url =~ %r{http://api\.rubyonrails\.org/v\d+\.} + url + elsif edge + url.sub("api", "edgeapi") + else + url.sub(/(?<=\.org)/, "/#{version}") + end + end end end end diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 6976848e95..1020f4a8e7 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -10,7 +10,7 @@ Rails 2.3 delivers a variety of new and improved features, including pervasive R Application Architecture ------------------------ -There are two major changes in the architecture of Rails applications: complete integration of the [Rack](http://rack.github.io/) modular web server interface, and renewed support for Rails Engines. +There are two major changes in the architecture of Rails applications: complete integration of the [Rack](https://rack.github.io/) modular web server interface, and renewed support for Rails Engines. ### Rack Integration @@ -231,7 +231,7 @@ Rails chooses between file, template, and action depending on whether there is a ### Application Controller Renamed -If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be application_controller.rb in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process. +If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be `application_controller.rb` in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process. * More Information: * [The Death of Application.rb](http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/) @@ -304,7 +304,7 @@ Rails now keeps a per-request local cache of read from the remote cache stores, Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a `Posts` controller with a `show` action. By default, this will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :da`, it will render `app/views/posts/show.da.html.erb`. If the localized template isn't present, the undecorated version will be used. Rails also includes `I18n#available_locales` and `I18n::SimpleBackend#available_locales`, which return an array of the translations that are available in the current Rails project. -In addition, you can use the same scheme to localize the rescue files in the `public` directory: `public/500.da.html` or `public/404.en.html` work, for example. +In addition, you can use the same scheme to localize the rescue files in the public directory: `public/500.da.html` or `public/404.en.html` work, for example. ### Partial Scoping for Translations diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index 517b38be07..f0e2cb3b63 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -88,7 +88,7 @@ $ cd myapp Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/bundler/bundler) which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. -More information: - [bundler homepage](http://bundler.io/) +More information: - [bundler homepage](https://bundler.io/) ### Living on the Edge diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index fd90cf9886..17d4ac23b6 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -151,7 +151,7 @@ $ cd myapp Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/carlhuda/bundler) gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. -More information: - [bundler homepage](http://bundler.io/) +More information: - [bundler homepage](https://bundler.io/) ### Living on the Edge diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index f16d509f77..ae6eb27f35 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -30,13 +30,13 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails. ### What to update in your apps -* Update your Gemfile to depend on +* Update your `Gemfile` to depend on * `rails = 3.2.0` * `sass-rails ~> 3.2.3` * `coffee-rails ~> 3.2.1` * `uglifier >= 1.0.3` -* Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. +* Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. * There are a couple of new configuration changes you'd want to add in `config/environments/development.rb`: @@ -81,7 +81,7 @@ $ cd myapp Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/carlhuda/bundler) gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. -More information: [Bundler homepage](http://bundler.io/) +More information: [Bundler homepage](https://bundler.io/) ### Living on the Edge @@ -156,7 +156,7 @@ Railties will create indexes for `title` and `author` with the latter being a unique index. Some types such as decimal accept custom options. In the example, `price` will be a decimal column with precision and scale set to 7 and 2 respectively. -* Turn gem has been removed from default Gemfile. +* Turn gem has been removed from default `Gemfile`. * Remove old plugin generator `rails generate plugin` in favor of `rails plugin new` command. @@ -295,7 +295,7 @@ Action Pack ```ruby @items.each do |item| content_tag_for(:li, item) do - Title: <%= item.title %> + Title: <%= item.title %> end end ``` diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 4615cf18e6..0921cd1979 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -36,7 +36,7 @@ $ cd myapp Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/carlhuda/bundler) gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. -More information: [Bundler homepage](http://bundler.io) +More information: [Bundler homepage](https://bundler.io) ### Living on the Edge @@ -60,13 +60,13 @@ Major Features ### Upgrade * **Ruby 1.9.3** ([commit](https://github.com/rails/rails/commit/a0380e808d3dbd2462df17f5d3b7fcd8bd812496)) - Ruby 2.0 preferred; 1.9.3+ required -* **[New deprecation policy](http://www.youtube.com/watch?v=z6YgD6tVPQs)** - Deprecated features are warnings in Rails 4.0 and will be removed in Rails 4.1. +* **[New deprecation policy](https://www.youtube.com/watch?v=z6YgD6tVPQs)** - Deprecated features are warnings in Rails 4.0 and will be removed in Rails 4.1. * **ActionPack page and action caching** ([commit](https://github.com/rails/rails/commit/b0a7068564f0c95e7ef28fc39d0335ed17d93e90)) - Page and action caching are extracted to a separate gem. Page and action caching requires too much manual intervention (manually expiring caches when the underlying model objects are updated). Instead, use Russian doll caching. * **ActiveRecord observers** ([commit](https://github.com/rails/rails/commit/ccecab3ba950a288b61a516bf9b6962e384aae0b)) - Observers are extracted to a separate gem. Observers are only needed for page and action caching, and can lead to spaghetti code. * **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store. * **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters. * **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used. -* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems. +* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a `Gemfile` to manage installed gems. ### ActionPack diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 6bf65757ec..2c5e665e33 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -274,7 +274,7 @@ for detailed changes. * The [Spring application preloader](https://github.com/rails/spring) is now installed by default for new applications. It uses the development group of - the Gemfile, so will not be installed in + the `Gemfile`, so will not be installed in production. ([Pull Request](https://github.com/rails/rails/pull/12958)) * `BACKTRACE` environment variable to show unfiltered backtraces for test diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index a30bfc458a..7105df5634 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -179,7 +179,7 @@ change your code to use the explicit form (`render file: "foo/bar"`) instead. `respond_with` and the corresponding class-level `respond_to` have been moved to the [responders](https://github.com/plataformatec/responders) gem. Add -`gem 'responders', '~> 2.0'` to your Gemfile to use it: +`gem 'responders', '~> 2.0'` to your `Gemfile` to use it: ```ruby # app/controllers/users_controller.rb @@ -368,7 +368,7 @@ Please refer to the [Changelog][railties] for detailed changes. ### Notable changes -* Introduced `web-console` in the default application Gemfile. +* Introduced `web-console` in the default application `Gemfile`. ([Pull Request](https://github.com/rails/rails/pull/11667)) * Added a `required` option to the model generator for associations. diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 24fb0ca6e1..656838c6b8 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -55,7 +55,7 @@ information. ### API Applications Rails can now be used to create slimmed down API only applications. -This is useful for creating and serving APIs similar to [Twitter](https://dev.twitter.com) or [GitHub](http://developer.github.com) API, +This is useful for creating and serving APIs similar to [Twitter](https://dev.twitter.com) or [GitHub](https://developer.github.com) API, that can be used to serve public facing, as well as, for custom applications. You can generate a new api Rails app using: @@ -74,11 +74,11 @@ This will do three main things: Controller modules that provide functionalities primarily used by browser applications. - Configure the generators to skip generating views, helpers and assets when - you generate a new resource. + you generate a new resource. + +The application provides a base for APIs, +that can then be [configured to pull in functionality](api_app.html) as suitable for the application's needs. -The application provides a base for APIs, -that can then be [configured to pull in functionality](api_app.html) as suitable for the application's needs. - See the [Using Rails for API-only Applications](api_app.html) guide for more information. @@ -150,7 +150,7 @@ The type of an attribute is given the opportunity to change how dirty tracking is performed. See its -[documentation](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html) +[documentation](http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html) for a detailed write up. @@ -242,7 +242,7 @@ Please refer to the [Changelog][railties] for detailed changes. [Pull Request](https://github.com/rails/rails/pull/22288)) * New applications are generated with the evented file system monitor enabled - on Linux and Mac OS X. The feature can be opted out by passing + on Linux and macOS. The feature can be opted out by passing `--skip-listen` to the generator. ([commit](https://github.com/rails/rails/commit/de6ad5665d2679944a9ee9407826ba88395a1003), [commit](https://github.com/rails/rails/commit/94dbc48887bf39c241ee2ce1741ee680d773f202)) @@ -775,7 +775,7 @@ Please refer to the [Changelog][active-record] for detailed changes. * Added prepared statements support to `mysql2` adapter, for mysql2 0.4.4+, Previously this was only supported on the deprecated `mysql` legacy adapter. - To enable, set `prepared_statements: true` in config/database.yml. + To enable, set `prepared_statements: true` in `config/database.yml`. ([Pull Request](https://github.com/rails/rails/pull/23461)) * Added ability to call `ActionRecord::Relation#update` on relation objects diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md new file mode 100644 index 0000000000..852d04b1f6 --- /dev/null +++ b/guides/source/5_1_release_notes.md @@ -0,0 +1,659 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Ruby on Rails 5.1 Release Notes +=============================== + +Highlights in Rails 5.1: + +* Yarn Support +* Optional Webpack support +* jQuery no longer a default dependency +* System tests +* Encrypted secrets +* Parameterized mailers +* Direct & resolved routes +* Unification of form_for and form_tag into form_with + +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 +commits](https://github.com/rails/rails/commits/5-1-stable) in the main Rails +repository on GitHub. + +-------------------------------------------------------------------------------- + +Upgrading to Rails 5.1 +---------------------- + +If you're upgrading an existing application, it's a great idea to have good test +coverage before going in. You should also first upgrade to Rails 5.0 in case you +haven't and make sure your application still runs as expected before attempting +an update to Rails 5.1. A list of things to watch out for when upgrading is +available in the +[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-5-0-to-rails-5-1) +guide. + + +Major Features +-------------- + +### Yarn Support + +[Pull Request](https://github.com/rails/rails/pull/26836) + +Rails 5.1 allows managing JavaScript dependencies +from NPM via Yarn. This will make it easy to use libraries like React, VueJS +or any other library from NPM world. The Yarn support is integrated with +the asset pipeline so that all dependencies will work seamlessly with the +Rails 5.1 app. + +### Optional Webpack support + +[Pull Request](https://github.com/rails/rails/pull/27288) + +Rails apps can integrate with [Webpack](https://webpack.js.org/), a JavaScript +asset bundler, more easily using the new [Webpacker](https://github.com/rails/webpacker) +gem. Use the `--webpack` flag when generating new applications to enable Webpack +integration. + +This is fully compatible with the asset pipeline, which you can continue to use for +images, fonts, sounds, and other assets. You can even have some JavaScript code +managed by the asset pipeline, and other code processed via Webpack. All of this is managed +by Yarn, which is enabled by default. + +### jQuery no longer a default dependency + +[Pull Request](https://github.com/rails/rails/pull/27113) + +jQuery was required by default in earlier versions of Rails to provide features +like `data-remote`, `data-confirm` and other parts of Rails' Unobtrusive JavaScript +offerings. It is no longer required, as the UJS has been rewritten to use plain, +vanilla JavaScript. This code now ships inside of Action View as +`rails-ujs`. + +You can still use jQuery if needed, but it is no longer required by default. + +### System tests + +[Pull Request](https://github.com/rails/rails/pull/26703) + +Rails 5.1 has baked-in support for writing Capybara tests, in the form of +System tests. You no longer need to worry about configuring Capybara and +database cleaning strategies for such tests. Rails 5.1 provides a wrapper +for running tests in Chrome with additional features such as failure +screenshots. + +### Encrypted secrets + +[Pull Request](https://github.com/rails/rails/pull/28038) + +Rails now allows management of application secrets in a secure way, +inspired by the [sekrets](https://github.com/ahoward/sekrets) gem. + +Run `bin/rails secrets:setup` to setup a new encrypted secrets file. This will +also generate a master key, which must be stored outside of the repository. The +secrets themselves can then be safely checked into the revision control system, +in an encrypted form. + +Secrets will be decrypted in production, using a key stored either in the +`RAILS_MASTER_KEY` environment variable, or in a key file. + +### Parameterized mailers + +[Pull Request](https://github.com/rails/rails/pull/27825) + +Allows specifying common parameters used for all methods in a mailer class in +order to share instance variables, headers and other common setup. + +``` ruby +class InvitationsMailer < ApplicationMailer + before_action { @inviter, @invitee = params[:inviter], params[:invitee] } + before_action { @account = params[:inviter].account } + + def account_invitation + mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})" + end +end + +InvitationsMailer.with(inviter: person_a, invitee: person_b) + .account_invitation.deliver_later +``` + +### Direct & resolved routes + +[Pull Request](https://github.com/rails/rails/pull/23138) + +Rails 5.1 adds two new methods, `resolve` and `direct`, to the routing +DSL. The `resolve` method allows customizing polymorphic mapping of models. + +``` ruby +resource :basket + +resolve("Basket") { [:basket] } +``` + +``` erb +<%= form_for @basket do |form| %> + <!-- basket form --> +<% end %> +``` + +This will generate the singular URL `/basket` instead of the usual `/baskets/:id`. + +The `direct` method allows creation of custom URL helpers. + +``` ruby +direct(:homepage) { "http://www.rubyonrails.org" } + +>> homepage_url +=> "http://www.rubyonrails.org" +``` + +The return value of the block must be a valid argument for the `url_for` +method. So, you can pass a valid string URL, Hash, Array, an +Active Model instance, or an Active Model class. + +``` ruby +direct :commentable do |model| + [ model, anchor: model.dom_id ] +end + +direct :main do + { controller: 'pages', action: 'index', subdomain: 'www' } +end +``` + +### Unification of form_for and form_tag into form_with + +[Pull Request](https://github.com/rails/rails/pull/26976) + +Before Rails 5.1, there were two interfaces for handling HTML forms: +`form_for` for model instances and `form_tag` for custom URLs. + +Rails 5.1 combines both of these interfaces with `form_with`, and +can generate form tags based on URLs, scopes or models. + +Using just a URL: + +``` erb +<%= form_with url: posts_path do |form| %> + <%= form.text_field :title %> +<% end %> + +<%# Will generate %> + +<form action="/posts" method="post" data-remote="true"> + <input type="text" name="title"> +</form> +``` + +Adding a scope prefixes the input field names: + +``` erb +<%= form_with scope: :post, url: posts_path do |form| %> + <%= form.text_field :title %> +<% end %> + +<%# Will generate %> + +<form action="/posts" method="post" data-remote="true"> + <input type="text" name="post[title]"> +</form> +``` + +Using a model infers both the URL and scope: + +``` erb +<%= form_with model: Post.new do |form| %> + <%= form.text_field :title %> +<% end %> + +<%# Will generate %> + +<form action="/posts" method="post" data-remote="true"> + <input type="text" name="post[title]"> +</form> +``` + +An existing model makes an update form and fills out field values: + +``` erb +<%= form_with model: Post.first do |form| %> + <%= form.text_field :title %> +<% end %> + +<%# Will generate %> + +<form action="/posts/1" method="post" data-remote="true"> + <input type="hidden" name="_method" value="patch"> + <input type="text" name="post[title]" value="<the title of the post>"> +</form> +``` + +Incompatibilities +----------------- + +The following changes may require immediate action upon upgrade. + +### Transactional tests with multiple connections + +Transactional tests now wrap all Active Record connections in database +transactions. + +When a test spawns additional threads, and those threads obtain database +connections, those connections are now handled specially: + +The threads will share a single connection, which is inside the managed +transaction. This ensures all threads see the database in the same +state, ignoring the outermost transaction. Previously, such additional +connections were unable to see the fixture rows, for example. + +When a thread enters a nested transaction, it will temporarily obtain +exclusive use of the connection, to maintain isolation. + +If your tests currently rely on obtaining a separate, +outside-of-transaction, connection in a spawned thread, you'll need to +switch to more explicit connection management. + +If your tests spawn threads and those threads interact while also using +explicit database transactions, this change may introduce a deadlock. + +The easy way to opt out of this new behavior is to disable transactional +tests on any test cases it affects. + +Railties +-------- + +Please refer to the [Changelog][railties] for detailed changes. + +### Removals + +* Remove deprecated `config.static_cache_control`. + ([commit](https://github.com/rails/rails/commit/c861decd44198f8d7d774ee6a74194d1ac1a5a13)) + +* Remove deprecated `config.serve_static_files`. + ([commit](https://github.com/rails/rails/commit/0129ca2eeb6d5b2ea8c6e6be38eeb770fe45f1fa)) + +* Remove deprecated file `rails/rack/debugger`. + ([commit](https://github.com/rails/rails/commit/7563bf7b46e6f04e160d664e284a33052f9804b8)) + +* Remove deprecated tasks: `rails:update`, `rails:template`, `rails:template:copy`, + `rails:update:configs` and `rails:update:bin`. + ([commit](https://github.com/rails/rails/commit/f7782812f7e727178e4a743aa2874c078b722eef)) + +* Remove deprecated `CONTROLLER` environment variable for `routes` task. + ([commit](https://github.com/rails/rails/commit/f9ed83321ac1d1902578a0aacdfe55d3db754219)) + +* Remove -j (--javascript) option from `rails new` command. + ([Pull Request](https://github.com/rails/rails/pull/28546)) + +### Notable changes + +* Added a shared section to `config/secrets.yml` that will be loaded for all + environments. + ([commit](https://github.com/rails/rails/commit/e530534265d2c32b5c5f772e81cb9002dcf5e9cf)) + +* The config file `config/secrets.yml` is now loaded in with all keys as symbols. + ([Pull Request](https://github.com/rails/rails/pull/26929)) + +* Removed jquery-rails from default stack. rails-ujs, which is shipped + with Action View, is included as default UJS adapter. + ([Pull Request](https://github.com/rails/rails/pull/27113)) + +* Add Yarn support in new apps with a yarn binstub and package.json. + ([Pull Request](https://github.com/rails/rails/pull/26836)) + +* Add Webpack support in new apps via the `--webpack` option, which will delegate + to the rails/webpacker gem. + ([Pull Request](https://github.com/rails/rails/pull/27288)) + +* Initialize Git repo when generating new app, if option `--skip-git` is not + provided. + ([Pull Request](https://github.com/rails/rails/pull/27632)) + +* Add encrypted secrets in `config/secrets.yml.enc`. + ([Pull Request](https://github.com/rails/rails/pull/28038)) + +* Display railtie class name in `rails initializers`. + ([Pull Request](https://github.com/rails/rails/pull/25257)) + +Action Cable +----------- + +Please refer to the [Changelog][action-cable] for detailed changes. + +### Notable changes + +* Added support for `channel_prefix` to Redis and evented Redis adapters + in `cable.yml` to avoid name collisions when using the same Redis server + with multiple applications. + ([Pull Request](https://github.com/rails/rails/pull/27425)) + +* Add `ActiveSupport::Notifications` hook for broadcasting data. + ([Pull Request](https://github.com/rails/rails/pull/24988)) + +Action Pack +----------- + +Please refer to the [Changelog][action-pack] for detailed changes. + +### Removals + +* Removed support for non-keyword arguments in `#process`, `#get`, `#post`, + `#patch`, `#put`, `#delete`, and `#head` for the `ActionDispatch::IntegrationTest` + and `ActionController::TestCase` classes. + ([Commit](https://github.com/rails/rails/commit/98b8309569a326910a723f521911e54994b112fb), + [Commit](https://github.com/rails/rails/commit/de9542acd56f60d281465a59eac11e15ca8b3323)) + +* Removed deprecated `ActionDispatch::Callbacks.to_prepare` and + `ActionDispatch::Callbacks.to_cleanup`. + ([Commit](https://github.com/rails/rails/commit/3f2b7d60a52ffb2ad2d4fcf889c06b631db1946b)) + +* Removed deprecated methods related to controller filters. + ([Commit](https://github.com/rails/rails/commit/d7be30e8babf5e37a891522869e7b0191b79b757)) + +* Removed deprecated support to `:text` and `:nothing` in `render`. + ([Commit](https://github.com/rails/rails/commit/79a5ea9eadb4d43b62afacedc0706cbe88c54496), + [Commit](https://github.com/rails/rails/commit/57e1c99a280bdc1b324936a690350320a1cd8111)) + +* Removed deprecated support for calling `HashWithIndifferentAccess` methods on `ActionController::Parameters`. + ([Commit](https://github.com/rails/rails/pull/26746/commits/7093ceb480ad6a0a91b511832dad4c6a86981b93)) + +### Deprecations + +* Deprecated `config.action_controller.raise_on_unfiltered_parameters`. + It doesn't have any effect in Rails 5.1. + ([Commit](https://github.com/rails/rails/commit/c6640fb62b10db26004a998d2ece98baede509e5)) + +### Notable changes + +* Added the `direct` and `resolve` methods to the routing DSL. + ([Pull Request](https://github.com/rails/rails/pull/23138)) + +* Added a new `ActionDispatch::SystemTestCase` class to write system tests in + your applications. + ([Pull Request](https://github.com/rails/rails/pull/26703)) + +Action View +------------- + +Please refer to the [Changelog][action-view] for detailed changes. + +### Removals + +* Removed deprecated `#original_exception` in `ActionView::Template::Error`. + ([commit](https://github.com/rails/rails/commit/b9ba263e5aaa151808df058f5babfed016a1879f)) + +* Remove the option `encode_special_chars` misnomer from `strip_tags`. + ([Pull Request](https://github.com/rails/rails/pull/28061)) + +### Deprecations + +* Deprecated Erubis ERB handler in favor of Erubi. + ([Pull Request](https://github.com/rails/rails/pull/27757)) + +### Notable changes + +* Raw template handler (the default template handler in Rails 5) now outputs + HTML-safe strings. + ([commit](https://github.com/rails/rails/commit/1de0df86695f8fa2eeae6b8b46f9b53decfa6ec8)) + +* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local` + fields. + ([Pull Request](https://github.com/rails/rails/pull/28061)) + +* New Builder-style syntax for HTML tags (`tag.div`, `tag.br`, etc.) + ([Pull Request](https://github.com/rails/rails/pull/25543)) + +* Add `form_with` to unify `form_tag` and `form_for` usage. + ([Pull Request](https://github.com/rails/rails/pull/26976)) + +* Add `check_parameters` option to `current_page?`. + ([Pull Request](https://github.com/rails/rails/pull/27549)) + +Action Mailer +------------- + +Please refer to the [Changelog][action-mailer] for detailed changes. + +### Notable changes + +* Allowed setting custom content type when attachments are included + and body is set inline. + ([Pull Request](https://github.com/rails/rails/pull/27227)) + +* Allowed passing lambdas as values to the `default` method. + ([Commit](https://github.com/rails/rails/commit/1cec84ad2ddd843484ed40b1eb7492063ce71baf)) + +* Added support for parameterized invocation of mailers to share before filters and defaults + between different mailer actions. + ([Commit](https://github.com/rails/rails/commit/1cec84ad2ddd843484ed40b1eb7492063ce71baf)) + +* Passed the incoming arguments to the mailer action to `process.action_mailer` event under + an `args` key. + ([Pull Request](https://github.com/rails/rails/pull/27900)) + +Active Record +------------- + +Please refer to the [Changelog][active-record] for detailed changes. + +### Removals + +* Removed support for passing arguments and block at the same time to + `ActiveRecord::QueryMethods#select`. + ([Commit](https://github.com/rails/rails/commit/4fc3366d9d99a0eb19e45ad2bf38534efbf8c8ce)) + +* Removed deprecated `activerecord.errors.messages.restrict_dependent_destroy.one` and + `activerecord.errors.messages.restrict_dependent_destroy.many` i18n scopes. + ([Commit](https://github.com/rails/rails/commit/00e3973a311)) + +* Removed deprecated force reload argument in singular and collection association readers. + ([Commit](https://github.com/rails/rails/commit/09cac8c67af)) + +* Removed deprecated support for passing a column to `#quote`. + ([Commit](https://github.com/rails/rails/commit/e646bad5b7c)) + +* Removed deprecated `name` arguments from `#tables`. + ([Commit](https://github.com/rails/rails/commit/d5be101dd02214468a27b6839ffe338cfe8ef5f3)) + +* Removed deprecated behavior of `#tables` and `#table_exists?` to return tables and views + to return only tables and not views. + ([Commit](https://github.com/rails/rails/commit/5973a984c369a63720c2ac18b71012b8347479a8)) + +* Removed deprecated `original_exception` argument in `ActiveRecord::StatementInvalid#initialize` + and `ActiveRecord::StatementInvalid#original_exception`. + ([Commit](https://github.com/rails/rails/commit/bc6c5df4699d3f6b4a61dd12328f9e0f1bd6cf46)) + +* Removed deprecated support of passing a class as a value in a query. + ([Commit](https://github.com/rails/rails/commit/b4664864c972463c7437ad983832d2582186e886)) + +* Removed deprecated support to query using commas on LIMIT. + ([Commit](https://github.com/rails/rails/commit/fc3e67964753fb5166ccbd2030d7382e1976f393)) + +* Removed deprecated `conditions` parameter from `#destroy_all`. + ([Commit](https://github.com/rails/rails/commit/d31a6d1384cd740c8518d0bf695b550d2a3a4e9b)) + +* Removed deprecated `conditions` parameter from `#delete_all`. + ([Commit](https://github.com/rails/rails/pull/27503/commits/e7381d289e4f8751dcec9553dcb4d32153bd922b)) + +* Removed deprecated method `#load_schema_for` in favor of `#load_schema`. + ([Commit](https://github.com/rails/rails/commit/419e06b56c3b0229f0c72d3e4cdf59d34d8e5545)) + +* Removed deprecated `#raise_in_transactional_callbacks` configuration. + ([Commit](https://github.com/rails/rails/commit/8029f779b8a1dd9848fee0b7967c2e0849bf6e07)) + +* Removed deprecated `#use_transactional_fixtures` configuration. + ([Commit](https://github.com/rails/rails/commit/3955218dc163f61c932ee80af525e7cd440514b3)) + +### Deprecations + +* Deprecated `error_on_ignored_order_or_limit` flag in favor of + `error_on_ignored_order`. + ([Commit](https://github.com/rails/rails/commit/451437c6f57e66cc7586ec966e530493927098c7)) + +* Deprecated `sanitize_conditions` in favor of `sanitize_sql`. + ([Pull Request](https://github.com/rails/rails/pull/25999)) + +* Deprecated `supports_migrations?` on connection adapters. + ([Pull Request](https://github.com/rails/rails/pull/28172)) + +* Deprecated `Migrator.schema_migrations_table_name`, use `SchemaMigration.table_name` instead. + ([Pull Request](https://github.com/rails/rails/pull/28351)) + +* Deprecated using `#quoted_id` in quoting and type casting. + ([Pull Request](https://github.com/rails/rails/pull/27962)) + +* Deprecated passing `default` argument to `#index_name_exists?`. + ([Pull Request](https://github.com/rails/rails/pull/26930)) + +### Notable changes + +* Change Default Primary Keys to BIGINT. + ([Pull Request](https://github.com/rails/rails/pull/26266)) + +* Virtual/generated column support for MySQL 5.7.5+ and MariaDB 5.2.0+. + ([Commit](https://github.com/rails/rails/commit/65bf1c60053e727835e06392d27a2fb49665484c)) + +* Added support for limits in batch processing. + ([Commit](https://github.com/rails/rails/commit/451437c6f57e66cc7586ec966e530493927098c7)) + +* Transactional tests now wrap all Active Record connections in database + transactions. + ([Pull Request](https://github.com/rails/rails/pull/28726)) + +* Skipped comments in the output of `mysqldump` command by default. + ([Pull Request](https://github.com/rails/rails/pull/23301)) + +* Fixed `ActiveRecord::Relation#count` to use Ruby's `Enumerable#count` for counting + records when a block is passed as argument instead of silently ignoring the + passed block. + ([Pull Request](https://github.com/rails/rails/pull/24203)) + +* Pass `"-v ON_ERROR_STOP=1"` flag with `psql` command to not suppress SQL errors. + ([Pull Request](https://github.com/rails/rails/pull/24773)) + +* Add `ActiveRecord::Base.connection_pool.stat`. + ([Pull Request](https://github.com/rails/rails/pull/26988)) + +* Inheriting directly from `ActiveRecord::Migration` raises an error. + Specify the Rails version for which the migration was written for. + ([Commit](https://github.com/rails/rails/commit/249f71a22ab21c03915da5606a063d321f04d4d3)) + +* An error is raised when `through` association has ambiguous reflection name. + ([Commit](https://github.com/rails/rails/commit/0944182ad7ed70d99b078b22426cbf844edd3f61)) + +Active Model +------------ + +Please refer to the [Changelog][active-model] for detailed changes. + +### Removals + +* Removed deprecated methods in `ActiveModel::Errors`. + ([commit](https://github.com/rails/rails/commit/9de6457ab0767ebab7f2c8bc583420fda072e2bd)) + +* Removed deprecated `:tokenizer` option in the length validator. + ([commit](https://github.com/rails/rails/commit/6a78e0ecd6122a6b1be9a95e6c4e21e10e429513)) + +* Remove deprecated behavior that halts callbacks when the return value is false. + ([commit](https://github.com/rails/rails/commit/3a25cdca3e0d29ee2040931d0cb6c275d612dffe)) + +### Notable changes + +* The original string assigned to a model attribute is no longer incorrectly + frozen. + ([Pull Request](https://github.com/rails/rails/pull/28729)) + +Active Job +----------- + +Please refer to the [Changelog][active-job] for detailed changes. + +### Removals + +* Removed deprecated support to passing the adapter class to `.queue_adapter`. + ([commit](https://github.com/rails/rails/commit/d1fc0a5eb286600abf8505516897b96c2f1ef3f6)) + +* Removed deprecated `#original_exception` in `ActiveJob::DeserializationError`. + ([commit](https://github.com/rails/rails/commit/d861a1fcf8401a173876489d8cee1ede1cecde3b)) + +### Notable changes + +* Added declarative exception handling via `ActiveJob::Base.retry_on` and `ActiveJob::Base.discard_on`. + ([Pull Request](https://github.com/rails/rails/pull/25991)) + +* Yield the job instance so you have access to things like `job.arguments` on + the custom logic after retries fail. + ([commit](https://github.com/rails/rails/commit/a1e4c197cb12fef66530a2edfaeda75566088d1f)) + +Active Support +-------------- + +Please refer to the [Changelog][active-support] for detailed changes. + +### Removals + +* Removed the `ActiveSupport::Concurrency::Latch` class. + ([Commit](https://github.com/rails/rails/commit/0d7bd2031b4054fbdeab0a00dd58b1b08fb7fea6)) + +* Removed `halt_callback_chains_on_return_false`. + ([Commit](https://github.com/rails/rails/commit/4e63ce53fc25c3bc15c5ebf54bab54fa847ee02a)) + +* Removed deprecated behavior that halts callbacks when the return is false. + ([Commit](https://github.com/rails/rails/commit/3a25cdca3e0d29ee2040931d0cb6c275d612dffe)) + +### Deprecations + +* The top level `HashWithIndifferentAccess` class has been softly deprecated + in favor of the `ActiveSupport::HashWithIndifferentAccess` one. + ([Pull Request](https://github.com/rails/rails/pull/28157)) + +* Deprecated passing string to `:if` and `:unless` conditional options on `set_callback` and `skip_callback`. + ([Commit](https://github.com/rails/rails/commit/0952552)) + +### Notable changes + +* Fixed duration parsing and traveling to make it consistent across DST changes. + ([Commit](https://github.com/rails/rails/commit/8931916f4a1c1d8e70c06063ba63928c5c7eab1e), + [Pull Request](https://github.com/rails/rails/pull/26597)) + +* Updated Unicode to version 9.0.0. + ([Pull Request](https://github.com/rails/rails/pull/27822)) + +* Add Duration#before and #after as aliases for #ago and #since. + ([Pull Request](https://github.com/rails/rails/pull/27721)) + +* Added `Module#delegate_missing_to` to delegate method calls not + defined for the current object to a proxy object. + ([Pull Request](https://github.com/rails/rails/pull/23930)) + +* Added `Date#all_day` which returns a range representing the whole day + of the current date & time. + ([Pull Request](https://github.com/rails/rails/pull/24930)) + +* Introduced the `assert_changes` and `assert_no_changes` methods for tests. + ([Pull Request](https://github.com/rails/rails/pull/25393)) + +* The `travel` and `travel_to` methods now raise on nested calls. + ([Pull Request](https://github.com/rails/rails/pull/24890)) + +* Update `DateTime#change` to support usec and nsec. + ([Pull Request](https://github.com/rails/rails/pull/28242)) + +Credits +------- + +See the +[full list of contributors to Rails](http://contributors.rubyonrails.org/) for +the many people who spent many hours making Rails, the stable and robust +framework it is. Kudos to all of them. + +[railties]: https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md +[action-pack]: https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md +[action-view]: https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md +[action-mailer]: https://github.com/rails/rails/blob/5-1-stable/actionmailer/CHANGELOG.md +[action-cable]: https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md +[active-record]: https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md +[active-model]: https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md +[active-support]: https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md +[active-job]: https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md new file mode 100644 index 0000000000..7481d8df08 --- /dev/null +++ b/guides/source/5_2_release_notes.md @@ -0,0 +1,211 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Ruby on Rails 5.2 Release Notes +=============================== + +Highlights in Rails 5.2: + +* Active Storage +* Redis Cache Store +* HTTP/2 Early hints support +* Credentials +* Default Content Security Policy + +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 +commits](https://github.com/rails/rails/commits/5-2-stable) in the main Rails +repository on GitHub. + +-------------------------------------------------------------------------------- + +Upgrading to Rails 5.2 +---------------------- + +If you're upgrading an existing application, it's a great idea to have good test +coverage before going in. You should also first upgrade to Rails 5.1 in case you +haven't and make sure your application still runs as expected before attempting +an update to Rails 5.2. + + +Major Features +-------------- + +### Active Storage + +[README](https://github.com/rails/rails/blob/d3893ec38ec61282c2598b01a298124356d6b35a/activestorage/README.md) + +### Redis Cache Store + +[Pull Request](https://github.com/rails/rails/pull/31134) + + +### HTTP/2 Early hints support + +[Pull Request](https://github.com/rails/rails/pull/30744) + + +### Credentials + +[Pull Request](https://github.com/rails/rails/pull/30067) + + +### Default Content Security Policy + +[Pull Request](https://github.com/rails/rails/pull/31162) + +Incompatibilities +----------------- + +ToDo + +Railties +-------- + +Please refer to the [Changelog][railties] for detailed changes. + +### Deprecations + +* Deprecate `capify!` method in generators and templates. + ([Pull Request](https://github.com/rails/rails/pull/29493)) + +* Deprecated passing the environment's name as a regular argument to the + `rails dbconsole` and `rails console` commands. + ([Pull Request](https://github.com/rails/rails/pull/29358)) + +* Deprecated using subclass of `Rails::Application` to start the Rails server. + ([Pull Request](https://github.com/rails/rails/pull/30127)) + +* Deprecated `after_bundle` callback in Rails plugin templates. + ([Pull Request](https://github.com/rails/rails/pull/29446)) + +### Notable changes + +ToDo + +Action Cable +----------- + +Please refer to the [Changelog][action-cable] for detailed changes. + +### Notable changes + +ToDo + +Action Pack +----------- + +Please refer to the [Changelog][action-pack] for detailed changes. + +### Removals + +ToDo + +### Deprecations + +ToDo + +### Notable changes + +ToDo + +Action View +------------- + +Please refer to the [Changelog][action-view] for detailed changes. + +### Removals + +ToDo + +### Deprecations + +ToDo + +### Notable changes + +ToDo + +Action Mailer +------------- + +Please refer to the [Changelog][action-mailer] for detailed changes. + +### Notable changes + +ToDo + +Active Record +------------- + +Please refer to the [Changelog][active-record] for detailed changes. + +ToDo + +### Deprecations + +ToDo + +### Notable changes + +ToDo + +Active Model +------------ + +Please refer to the [Changelog][active-model] for detailed changes. + +### Removals + +ToDo + +### Notable changes + +ToDo + +Active Support +-------------- + +Please refer to the [Changelog][active-support] for detailed changes. + +### Removals + +ToDo + +### Deprecations + +ToDo + +### Notable changes + +ToDo + +Active Job +----------- + +Please refer to the [Changelog][active-job] for detailed changes. + +### Removals + +ToDo + +### Notable changes + +ToDo + +Credits +------- + +See the +[full list of contributors to Rails](http://contributors.rubyonrails.org/) for +the many people who spent many hours making Rails, the stable and robust +framework it is. Kudos to all of them. + +[railties]: https://github.com/rails/rails/blob/5-2-stable/railties/CHANGELOG.md +[action-pack]: https://github.com/rails/rails/blob/5-2-stable/actionpack/CHANGELOG.md +[action-view]: https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md +[action-mailer]: https://github.com/rails/rails/blob/5-2-stable/actionmailer/CHANGELOG.md +[action-cable]: https://github.com/rails/rails/blob/5-2-stable/actioncable/CHANGELOG.md +[active-record]: https://github.com/rails/rails/blob/5-2-stable/activerecord/CHANGELOG.md +[active-model]: https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md +[active-support]: https://github.com/rails/rails/blob/5-2-stable/activesupport/CHANGELOG.md +[active-job]: https://github.com/rails/rails/blob/5-2-stable/activejob/CHANGELOG.md diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index f50bcddbe7..6959f992aa 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -1,8 +1,8 @@ -<h2>Ruby on Rails Guides (<%= @edge ? @version[0, 7] : @version %>)</h2> +<h2>Ruby on Rails Guides (<%= @edge ? @edge[0, 7] : @version %>)</h2> <% if @edge %> <p> - These are <b>Edge Guides</b>, based on the current <a href="https://github.com/rails/rails/tree/<%= @version %>">master</a> branch. + These are <b>Edge Guides</b>, based on <a href="https://github.com/rails/rails/tree/<%= @edge %>">master@<%= @edge[0, 7] %></a>. </p> <p> If you are looking for the ones for the stable version, please check @@ -10,12 +10,14 @@ </p> <% else %> <p> - These are the new guides for Rails 5.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 5.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. </p> <% end %> <p> The guides for earlier releases: +<a href="http://guides.rubyonrails.org/v5.1/">Rails 5.1</a>, +<a href="http://guides.rubyonrails.org/v5.0/">Rails 5.0</a>, <a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>, <a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>, <a href="http://guides.rubyonrails.org/v4.0/">Rails 4.0</a>, diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index e929945dd0..c250db2e0c 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -1,12 +1,14 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Action Cable Overview ===================== -In this guide you will learn how Action Cable works and how to use WebSockets to +In this guide, you will learn how Action Cable works and how to use WebSockets to incorporate real-time features into your Rails application. After reading this guide, you will know: -* What Action Cable is and its integration on backend and frontend +* What Action Cable is and its integration backend and frontend * How to setup Action Cable * How to setup channels * Deployment and Architecture setup for running Action Cable @@ -64,8 +66,8 @@ module ApplicationCable private def find_verified_user - if current_user = User.find_by(id: cookies.signed[:user_id]) - current_user + if verified_user = User.find_by(id: cookies.encrypted[:user_id]) + verified_user else reject_unauthorized_connection end @@ -129,7 +131,7 @@ subscriptions based on an identifier sent by the cable consumer. # app/channels/chat_channel.rb class ChatChannel < ApplicationCable::Channel # Called when the consumer has successfully - # become a subscriber of this channel. + # become a subscriber to this channel. def subscribed end end @@ -225,7 +227,7 @@ A *broadcasting* is a pub/sub link where anything transmitted by a publisher is routed directly to the channel subscribers who are streaming that named broadcasting. Each channel can be streaming zero or more broadcastings. -Broadcastings are purely an online queue and time dependent. If a consumer is +Broadcastings are purely an online queue and time-dependent. If a consumer is not streaming (subscribed to a given channel), they'll not get the broadcast should they connect later. @@ -515,8 +517,8 @@ user. For a user with an ID of 1, the broadcasting name would be The channel has been instructed to stream everything that arrives at `web_notifications:1` directly to the client by invoking the `received` callback. The data passed as argument is the hash sent as the second parameter -to the server-side broadcast call, JSON encoded for the trip across the wire, -and unpacked for the data argument arriving to `received`. +to the server-side broadcast call, JSON encoded for the trip across the wire +and unpacked for the data argument arriving as `received`. ### More Complete Examples @@ -555,9 +557,8 @@ The async adapter is intended for development/testing and should not be used in ##### Redis Adapter -Action Cable contains two Redis adapters: "normal" Redis and Evented Redis. Both -of the adapters require users to provide a URL pointing to the Redis server. -Additionally, a channel_prefix may be provided to avoid channel name collisions +The Redis adapter requires users to provide a URL pointing to the Redis server. +Additionally, a `channel_prefix` may be provided to avoid channel name collisions when using the same Redis server for multiple applications. See the [Redis PubSub documentation](https://redis.io/topics/pubsub#database-amp-scoping) for more details. ##### PostgreSQL Adapter @@ -570,7 +571,7 @@ This may change in the future. [#27214](https://github.com/rails/rails/issues/27 Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of -strings or regular expressions, against which a check for match will be performed. +strings or regular expressions, against which a check for the match will be performed. ```ruby config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}] @@ -593,7 +594,7 @@ environment configuration files. ### Other Configurations -The other common option to configure, is the log tags applied to the +The other common option to configure is the log tags applied to the per-connection logger. Here's an example that uses the user account id if available, else "no-account" while tagging: @@ -608,7 +609,7 @@ 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 +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. @@ -669,8 +670,8 @@ authentication. You can see one way of doing that with Devise in this [article]( ## Dependencies Action Cable provides a subscription adapter interface to process its -pubsub internals. By default, asynchronous, inline, PostgreSQL, evented -Redis, and non-evented Redis adapters are included. The default adapter +pubsub internals. By default, asynchronous, inline, PostgreSQL, and Redis +adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 40eb838d32..6ecfb57db3 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -21,9 +21,9 @@ After reading this guide, you will know: What Does a Controller Do? -------------------------- -Action Controller is the C in MVC. After the router has determined which controller to use for a request, the controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. +Action Controller is the C in [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). After the router has determined which controller to use for a request, the controller is responsible for making sense of the request, and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. -For most conventional [RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work. +For most conventional [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work. A controller can thus be thought of as a middleman between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates user data to the model. @@ -61,7 +61,7 @@ end The [Layouts & Rendering Guide](layouts_and_rendering.html) explains this in more detail. -`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the API documentation or in the source itself. +`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the [API documentation](http://api.rubyonrails.org/classes/ActionController.html) or in the source itself. Only public methods are callable as actions. It is a best practice to lower the visibility of methods (with `private` or `protected`) which are not intended to be actions, like auxiliary methods or filters. @@ -397,34 +397,18 @@ You can also pass a `:domain` key and specify the domain name for the cookie: Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" ``` -Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml` +Rails sets up (for the CookieStore) a secret key used for signing the session data in `config/credentials.yml.enc`. This can be changed with `bin/rails credentials:edit`. ```ruby -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! - -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rails secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. +# aws: +# access_key_id: 123 +# secret_access_key: 345 -development: - secret_key_base: a75d... - -test: - secret_key_base: 492f... - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> +# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. +secret_key_base: 492f... ``` -NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions. +NOTE: Changing the secret_key_base when using the `CookieStore` will invalidate all existing sessions. ### Accessing the Session @@ -670,8 +654,8 @@ class UsersController < ApplicationController @users = User.all respond_to do |format| format.html # index.html.erb - format.xml { render xml: @users} - format.json { render json: @users} + format.xml { render xml: @users } + format.json { render json: @users } end end end @@ -715,11 +699,14 @@ end Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. +NOTE: Calling the same filter multiple times with different options will not work, +since the last filter definition will overwrite the previous ones. + ### After Filters and Around Filters In addition to "before" filters, you can also run filters after an action has been executed, or both before and after. -"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. +"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. Please note that "after" filters are executed only after a successful action, but not when an exception is raised in the request cycle. "around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work. @@ -797,9 +784,9 @@ The way this is done is to add a non-guessable token which is only known to your If you generate a form like this: ```erb -<%= form_for @user do |f| %> - <%= f.text_field :username %> - <%= f.text_field :password %> +<%= form_with model: @user, local: true do |form| %> + <%= form.text_field :username %> + <%= form.text_field :password %> <% end %> ``` @@ -1129,7 +1116,7 @@ Rails default exception handling displays a "500 Server Error" message for all e ### The Default 500 and 404 Templates -By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. +By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the public folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. ### `rescue_from` @@ -1183,7 +1170,7 @@ class ClientsController < ApplicationController end ``` -WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). +WARNING: Using `rescue_from` with `Exception` or `StandardError` would cause serious side-effects as it prevents Rails from handling exceptions properly. As such, it is not recommended to do so unless there is a strong reason. NOTE: When running in the production environment, all `ActiveRecord::RecordNotFound` errors render the 404 error page. Unless you need diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 380fdac658..cb07781d1c 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -64,7 +64,7 @@ Rails. Mailers are conceptually similar to controllers, and so we get a mailer, a directory for views, and a test. If you didn't want to use a generator, you could create your own file inside of -app/mailers, just make sure that it inherits from `ActionMailer::Base`: +`app/mailers`, just make sure that it inherits from `ActionMailer::Base`: ```ruby class MyMailer < ActionMailer::Base @@ -92,8 +92,8 @@ registered email address: class UserMailer < ApplicationMailer default from: 'notifications@example.com' - def welcome_email(user) - @user = user + def welcome_email + @user = params[:user] @url = 'http://example.com/login' mail(to: @user.email, subject: 'Welcome to My Awesome Site') end @@ -176,7 +176,7 @@ $ bin/rails db:migrate Now that we have a user model to play with, we will just edit the `app/controllers/users_controller.rb` make it instruct the `UserMailer` to deliver an email to the newly created user by editing the create action and inserting a -call to `UserMailer.welcome_email` right after the user is successfully saved. +call to `UserMailer.with(user: @user).welcome_email` right after the user is successfully saved. Action Mailer is nicely integrated with Active Job so you can send emails outside of the request-response cycle, so the user doesn't have to wait on it: @@ -191,7 +191,7 @@ class UsersController < ApplicationController respond_to do |format| if @user.save # Tell the UserMailer to send a welcome email after save - UserMailer.welcome_email(@user).deliver_later + UserMailer.with(user: @user).welcome_email.deliver_later format.html { redirect_to(@user, notice: 'User was successfully created.') } format.json { render json: @user, status: :created, location: @user } @@ -220,12 +220,17 @@ If you want to send emails right away (from a cronjob for example) just call class SendWeeklySummary def run User.find_each do |user| - UserMailer.weekly_summary(user).deliver_now + UserMailer.with(user: user).weekly_summary.deliver_now end end end ``` +Any key value pair passed to `with` just becomes the `params` for the mailer +action. So `with(user: @user, account: @user.account)` makes `params[:user]` and +`params[:account]` available in the mailer action. Just like controllers have +params. + The method `welcome_email` returns an `ActionMailer::MessageDelivery` object which can then just be told `deliver_now` or `deliver_later` to send itself out. The `ActionMailer::MessageDelivery` object is just a wrapper around a `Mail::Message`. If @@ -331,7 +336,7 @@ with the addresses separated by commas. ```ruby class AdminMailer < ApplicationMailer - default to: Proc.new { Admin.pluck(:email) }, + default to: -> { Admin.pluck(:email) }, from: 'notification@example.com' def new_registration(user) @@ -351,8 +356,8 @@ address when they receive the email. The trick to doing that is to format the email address in the format `"Full Name" <email>`. ```ruby -def welcome_email(user) - @user = user +def welcome_email + @user = params[:user] email_with_name = %("#{@user.name}" <#{@user.email}>) mail(to: email_with_name, subject: 'Welcome to My Awesome Site') end @@ -372,8 +377,8 @@ To change the default mailer view for your action you do something like: class UserMailer < ApplicationMailer default from: 'notifications@example.com' - def welcome_email(user) - @user = user + def welcome_email + @user = params[:user] @url = 'http://example.com/login' mail(to: @user.email, subject: 'Welcome to My Awesome Site', @@ -394,8 +399,8 @@ templates or even render inline or text without using a template file: class UserMailer < ApplicationMailer default from: 'notifications@example.com' - def welcome_email(user) - @user = user + def welcome_email + @user = params[:user] @url = 'http://example.com/login' mail(to: @user.email, subject: 'Welcome to My Awesome Site') do |format| @@ -413,7 +418,7 @@ inside of Action Controller, so you can use all the same options, such as #### Caching mailer view -You can do cache in mailer views like in application views using `cache` method. +You can perform fragment caching in mailer views like in application views using the `cache` method. ``` <% cache do %> @@ -427,6 +432,9 @@ And in order to use this feature, you need to configure your application with th config.action_mailer.perform_caching = true ``` +Fragment caching is also supported in multipart emails. +Read more about caching in the [Rails caching guide](caching_with_rails.html). + ### Action Mailer Layouts Just like controller views, you can also have mailer layouts. The layout name @@ -450,8 +458,8 @@ the format block to specify different layouts for different formats: ```ruby class UserMailer < ApplicationMailer - def welcome_email(user) - mail(to: user.email) do |format| + def welcome_email + mail(to: params[:user].email) do |format| format.html { render layout: 'my_layout' } format.text end @@ -474,7 +482,7 @@ special URL that renders them. In the above example, the preview class for ```ruby class UserMailerPreview < ActionMailer::Preview def welcome_email - UserMailer.welcome_email(User.first) + UserMailer.with(user: User.first).welcome_email end end ``` @@ -550,8 +558,9 @@ url helper. <%= user_url(@user, host: 'example.com') %> ``` -NOTE: non-`GET` links require [jQuery UJS](https://github.com/rails/jquery-ujs) -and won't work in mailer templates. They will result in normal `GET` requests. +NOTE: non-`GET` links require [rails-ujs](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts) or +[jQuery UJS](https://github.com/rails/jquery-ujs), and won't work in mailer templates. +They will result in normal `GET` requests. ### Adding images in Action Mailer Views @@ -559,7 +568,7 @@ Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the `:asset_host` parameter yourself. As the `:asset_host` usually is consistent across the application you can -configure it globally in config/application.rb: +configure it globally in `config/application.rb`: ```ruby config.action_mailer.asset_host = 'http://example.com' @@ -590,12 +599,12 @@ mailer action. ```ruby class UserMailer < ApplicationMailer - def welcome_email(user, company) - @user = user + def welcome_email + @user = params[:user] @url = user_url(@user) - delivery_options = { user_name: company.smtp_user, - password: company.smtp_password, - address: company.smtp_host } + delivery_options = { user_name: params[:company].smtp_user, + password: params[:company].smtp_password, + address: params[:company].smtp_host } mail(to: @user.email, subject: "Please see the Terms and Conditions attached", delivery_method_options: delivery_options) @@ -612,9 +621,9 @@ will default to `text/plain` otherwise. ```ruby class UserMailer < ApplicationMailer - def welcome_email(user, email_body) - mail(to: user.email, - body: email_body, + def welcome_email + mail(to: params[:user].email, + body: params[:email_body], content_type: "text/html", subject: "Already rendered!") end @@ -673,24 +682,43 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and * You could use a `before_action` to populate the mail object with defaults, delivery_method_options or insert default headers and attachments. +```ruby +class InvitationsMailer < ApplicationMailer + before_action { @inviter, @invitee = params[:inviter], params[:invitee] } + before_action { @account = params[:inviter].account } + + default to: -> { @invitee.email_address }, + from: -> { common_address(@inviter) }, + reply_to: -> { @inviter.email_address_with_name } + + def account_invitation + mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})" + end + + def project_invitation + @project = params[:project] + @summarizer = ProjectInvitationSummarizer.new(@project.bucket) + + mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" + end +end +``` + * You could use an `after_action` to do similar setup as a `before_action` but using instance variables set in your mailer action. ```ruby class UserMailer < ApplicationMailer + before_action { @business, @user = params[:business], params[:user] } + after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers - def feedback_message(business, user) - @business = business - @user = user - mail + def feedback_message end - def campaign_message(business, user) - @business = business - @user = user + def campaign_message end private @@ -780,7 +808,8 @@ config.action_mailer.smtp_settings = { enable_starttls_auto: true } ``` Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure. -You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or +You can change your Gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts. If your Gmail account has 2-factor authentication enabled, +then you will need to set an [app password](https://myaccount.google.com/apppasswords) and use that instead of your regular password. Alternatively, you can use another ESP to send email by replacing 'smtp.gmail.com' above with the address of your provider. Mailer Testing diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index c835adeab6..1cba5c6fb6 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -7,7 +7,7 @@ After reading this guide, you will know: * What Action View is and how to use it with Rails. * How best to use templates, partials, and layouts. -* What helpers are provided by Action View and how to make your own. +* What helpers are provided by Action View. * How to use localized views. -------------------------------------------------------------------------------- @@ -149,10 +149,10 @@ end #### Jbuilder [Jbuilder](https://github.com/rails/jbuilder) is a gem that's -maintained by the Rails team and included in the default Rails Gemfile. +maintained by the Rails team and included in the default Rails `Gemfile`. It's similar to Builder, but is used to generate JSON, instead of XML. -If you don't have it, you can add the following to your Gemfile: +If you don't have it, you can add the following to your `Gemfile`: ```ruby gem 'jbuilder' @@ -414,12 +414,12 @@ By default, Rails links to these assets on the current host in the public folder ```ruby config.action_controller.asset_host = "assets.example.com" -image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" alt="Rails" /> +image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" /> ``` #### auto_discovery_link_tag -Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed. +Returns a link tag that browsers and feed readers can use to auto-detect an RSS, Atom, or JSON feed. ```ruby auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", { title: "RSS Feed" }) # => @@ -453,7 +453,7 @@ image_url("edit.png") # => http://www.example.com/assets/edit.png Returns an HTML image tag for the source. The source can be a full path or a file that exists in your `app/assets/images` directory. ```ruby -image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" /> +image_tag("icon.png") # => <img src="/assets/icon.png" /> ``` #### javascript_include_tag @@ -464,25 +464,6 @@ Returns an HTML script tag for each of the sources provided. You can pass in the javascript_include_tag "common" # => <script src="/assets/common.js"></script> ``` -If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass `:defaults` as the source. When using `:defaults`, if an `application.js` file exists in your `app/assets/javascripts` directory, it will be included as well. - -```ruby -javascript_include_tag :defaults -``` - -You can also include all JavaScript files in the `app/assets/javascripts` directory using `:all` as the source. - -```ruby -javascript_include_tag :all -``` - -You can also cache multiple JavaScript files into one file, which requires less HTTP connections to download and can better be compressed by gzip (leading to faster transfers). Caching will only happen if `ActionController::Base.perform_caching` is set to true (which is the case by default for the Rails production environment, but not for the development environment). - -```ruby -javascript_include_tag :all, cache: true # => - <script src="/javascripts/all.js"></script> -``` - #### javascript_path Computes the path to a JavaScript asset in the `app/assets/javascripts` directory. If the source filename has no extension, `.js` will be appended. Full paths from the document root will be passed through. Used internally by `javascript_include_tag` to build the script path. @@ -507,22 +488,9 @@ Returns a stylesheet link tag for the sources specified as arguments. If you don stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" /> ``` -You can also include all styles in the stylesheet directory using `:all` as the source: - -```ruby -stylesheet_link_tag :all -``` - -You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching is set to true (which is the case by default for the Rails production environment, but not for the development environment). - -```ruby -stylesheet_link_tag :all, cache: true -# => <link href="/assets/all.css" media="screen" rel="stylesheet" /> -``` - #### stylesheet_path -Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. +Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by `stylesheet_link_tag` to build the stylesheet path. ```ruby stylesheet_path "application" # => /assets/application.css @@ -839,20 +807,22 @@ The core method of this helper, `form_for`, gives you the ability to create a fo The HTML generated for this would be: ```html -<form action="/people/create" method="post"> - <input id="person_first_name" name="person[first_name]" type="text" /> - <input id="person_last_name" name="person[last_name]" type="text" /> - <input name="commit" type="submit" value="Create" /> +<form class="new_person" id="new_person" action="/people" accept-charset="UTF-8" method="post"> + <input name="utf8" type="hidden" value="✓" /> + <input type="hidden" name="authenticity_token" value="lTuvBzs7ANygT0NFinXj98tfw3Emfm65wwYLbUvoWsK2pngccIQSUorM2C035M9dZswXgWTvKwFS8W5TVblpYw==" /> + <input type="text" name="person[first_name]" id="person_first_name" /> + <input type="text" name="person[last_name]" id="person_last_name" /> + <input type="submit" name="commit" value="Create" data-disable-with="Create" /> </form> ``` The params object created when this form is submitted would look like: ```ruby -{ "action" => "create", "controller" => "people", "person" => { "first_name" => "William", "last_name" => "Smith" } } +{"utf8" => "✓", "authenticity_token" => "lTuvBzs7ANygT0NFinXj98tfw3Emfm65wwYLbUvoWsK2pngccIQSUorM2C035M9dZswXgWTvKwFS8W5TVblpYw==", "person" => {"first_name" => "William", "last_name" => "Smith"}, "commit" => "Create", "controller" => "people", "action" => "create"} ``` -The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller. +The params hash has a nested person value, which can therefore be accessed with `params[:person]` in the controller. #### check_box @@ -1192,7 +1162,7 @@ Returns a string of option tags for pretty much any time zone in the world. Returns select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags. ```ruby -time_zone_select( "user", "time_zone") +time_zone_select("user", "time_zone") ``` #### date_field diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index c65d1e6de5..914ef2c327 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -114,7 +114,7 @@ For enqueuing and executing jobs in production you need to set up a queuing back that is to say you need to decide for a 3rd-party queuing library that Rails should use. Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM. If the process crashes or the machine is reset, then all outstanding jobs are lost with the -default async back-end. This may be fine for smaller apps or non-critical jobs, but most +default async backend. This may be fine for smaller apps or non-critical jobs, but most production apps will need to pick a persistent backend. ### Backends @@ -162,6 +162,7 @@ Here is a noncomprehensive list of documentation: - [Sidekiq](https://github.com/mperham/sidekiq/wiki/Active-Job) - [Resque](https://github.com/resque/resque/wiki/ActiveJob) +- [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) @@ -260,40 +261,48 @@ backends you need to specify the queues to listen to. Callbacks --------- -Active Job provides hooks during the life cycle of a job. Callbacks allow you to -trigger logic during the life cycle of a job. - -### Available callbacks - -* `before_enqueue` -* `around_enqueue` -* `after_enqueue` -* `before_perform` -* `around_perform` -* `after_perform` - -### Usage +Active Job provides hooks to trigger logic during the life cycle of a job. Like +other callbacks in Rails, you can implement the callbacks as ordinary methods +and use a macro-style class method to register them as callbacks: ```ruby class GuestsCleanupJob < ApplicationJob queue_as :default - before_enqueue do |job| - # Do something with the job instance - end - - around_perform do |job, block| - # Do something before perform - block.call - # Do something after perform - end + around_perform :around_cleanup def perform # Do something later end + + private + def around_cleanup(job) + # Do something before perform + yield + # Do something after perform + end +end +``` + +The macro-style class methods can also receive a block. Consider using this +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 + before_enqueue { |job| $statsd.increment "#{job.name.underscore}.enqueue" } end ``` +### Available callbacks + +* `before_enqueue` +* `around_enqueue` +* `after_enqueue` +* `before_perform` +* `around_perform` +* `after_perform` + Action Mailer ------------ @@ -310,6 +319,12 @@ UserMailer.welcome(@user).deliver_now UserMailer.welcome(@user).deliver_later ``` +NOTE: Using the asynchronous queue from a Rake task (for example, to +send an email using `.deliver_later`) will generally not work because Rake will +likely end, causing the in-process thread pool to be deleted, before any/all +of the `.deliver_later` emails are processed. To avoid this problem, use +`.deliver_now` or run a persistent queue in development. + Internationalization -------------------- @@ -365,7 +380,7 @@ class GuestsCleanupJob < ApplicationJob queue_as :default rescue_from(ActiveRecord::RecordNotFound) do |exception| - # Do something with the exception + # Do something with the exception end def perform @@ -374,6 +389,25 @@ class GuestsCleanupJob < ApplicationJob end ``` +### Retrying or Discarding failed jobs + +It's also possible to retry or discard a job if an exception is raised during execution. +For example: + +```ruby +class RemoteServiceJob < ApplicationJob + retry_on CustomAppException # defaults to 3s wait, 5 attempts + + discard_on ActiveJob::DeserializationError + + def perform(*args) + # Might raise CustomAppException or ActiveJob::DeserializationError + end +end +``` + +To get more details see the API Documentation for [ActiveJob::Exceptions](http://api.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html). + ### Deserialization GlobalID allows serializing full Active Record objects passed to `#perform`. diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index 732e553c62..ee0472621b 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -87,7 +87,7 @@ end ### Conversion If a class defines `persisted?` and `id` methods, then you can include the -`ActiveModel::Conversion` module in that class and call the Rails conversion +`ActiveModel::Conversion` module in that class, and call the Rails conversion methods on objects of that class. ```ruby @@ -156,16 +156,17 @@ person.changed? # => false person.first_name = "First Name" person.first_name # => "First Name" -# returns true if any of the attributes have unsaved changes, false otherwise. +# returns true if any of the attributes have unsaved changes. person.changed? # => true # returns a list of attributes that have changed before saving. person.changed # => ["first_name"] -# returns a hash of the attributes that have changed with their original values. +# returns a Hash of the attributes that have changed with their original values. person.changed_attributes # => {"first_name"=>nil} -# returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field. +# returns a Hash of changes, with the attribute names as the keys, and the +# values as an array of the old and new values for that field. person.changes # => {"first_name"=>[nil, "First Name"]} ``` @@ -179,7 +180,7 @@ person.first_name # => "First Name" person.first_name_changed? # => true ``` -Track what was the previous value of the attribute. +Track the previous value of the attribute. ```ruby # attr_name_was accessor @@ -187,7 +188,7 @@ person.first_name_was # => nil ``` Track both previous and current value of the changed attribute. Returns an array -if changed, else returns nil. +if changed, otherwise returns nil. ```ruby # attr_name_change @@ -197,7 +198,7 @@ person.last_name_change # => nil ### Validations -The `ActiveModel::Validations` module adds the ability to validate class objects +The `ActiveModel::Validations` module adds the ability to validate objects like in Active Record. ```ruby @@ -225,7 +226,7 @@ person.valid? # => raises ActiveModel::StrictValidationFa ### Naming -`ActiveModel::Naming` adds a number of class methods which make the naming and routing +`ActiveModel::Naming` adds a number of class methods which make naming and routing easier to manage. The module defines the `model_name` class method which will define a number of accessors using some `ActiveSupport::Inflector` methods. @@ -248,7 +249,7 @@ Person.model_name.singular_route_key # => "person" ### Model -`ActiveModel::Model` adds the ability to a class to work with Action Pack and +`ActiveModel::Model` adds the ability for a class to work with Action Pack and Action View right out of the box. ```ruby @@ -293,7 +294,7 @@ objects. ### Serialization `ActiveModel::Serialization` provides basic serialization for your object. -You need to declare an attributes hash which contains the attributes you want to +You need to declare an attributes Hash which contains the attributes you want to serialize. Attributes must be strings, not symbols. ```ruby @@ -308,7 +309,7 @@ class Person end ``` -Now you can access a serialized hash of your object using the `serializable_hash`. +Now you can access a serialized Hash of your object using the `serializable_hash` method. ```ruby person = Person.new @@ -319,13 +320,14 @@ person.serializable_hash # => {"name"=>"Bob"} #### ActiveModel::Serializers -Rails provides an `ActiveModel::Serializers::JSON` serializer. -This module automatically include the `ActiveModel::Serialization`. +Active Model also provides the `ActiveModel::Serializers::JSON` module +for JSON serializing / deserializing. This module automatically includes the +previously discussed `ActiveModel::Serialization` module. ##### ActiveModel::Serializers::JSON -To use the `ActiveModel::Serializers::JSON` you only need to change from -`ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`. +To use `ActiveModel::Serializers::JSON` you only need to change the +module you are including from `ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`. ```ruby class Person @@ -339,7 +341,8 @@ class Person end ``` -With the `as_json` method you have a hash representing the model. +The `as_json` method, similar to `serializable_hash`, provides a Hash representing +the model. ```ruby person = Person.new @@ -348,8 +351,8 @@ person.name = "Bob" person.as_json # => {"name"=>"Bob"} ``` -From a JSON string you define the attributes of the model. -You need to have the `attributes=` method defined on your class: +You can also define the attributes for a model from a JSON string. +However, you need to define the `attributes=` method on your class: ```ruby class Person @@ -369,7 +372,7 @@ class Person end ``` -Now it is possible to create an instance of person and set the attributes using `from_json`. +Now it is possible to create an instance of `Person` and set attributes using `from_json`. ```ruby json = { name: 'Bob' }.to_json @@ -389,8 +392,8 @@ class Person end ``` -With the `human_attribute_name` you can transform attribute names into a more -human format. The human format is defined in your locale file. +With the `human_attribute_name` method, you can transform attribute names into a +more human-readable format. The human-readable format is defined in your locale file(s). * config/locales/app.pt-BR.yml @@ -411,7 +414,7 @@ Person.human_attribute_name('name') # => "Nome" `ActiveModel::Lint::Tests` allows you to test whether an object is compliant with the Active Model API. -* app/models/person.rb +* `app/models/person.rb` ```ruby class Person @@ -419,7 +422,7 @@ the Active Model API. end ``` -* test/models/person_test.rb +* `test/models/person_test.rb` ```ruby require 'test_helper' @@ -454,19 +457,19 @@ features out of the box. ### SecurePassword `ActiveModel::SecurePassword` provides a way to securely store any -password in an encrypted form. On including this module, a +password in an encrypted form. When you include this module, a `has_secure_password` class method is provided which defines -an accessor named `password` with certain validations on it. +a `password` accessor with certain validations on it. #### Requirements `ActiveModel::SecurePassword` depends on [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), -so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly. +so include this gem in your `Gemfile` to use `ActiveModel::SecurePassword` correctly. In order to make this work, the model must have an accessor named `password_digest`. The `has_secure_password` will add the following validations on the `password` accessor: 1. Password should be present. -2. Password should be equal to its confirmation (provided +password_confirmation+ is passed along). +2. Password should be equal to its confirmation (provided `password_confirmation` is passed along). 3. The maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) #### Examples diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 6b3aa471f9..9be9c6c7b7 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -20,7 +20,7 @@ After reading this guide, you will know: What is Active Record? ---------------------- -Active Record is the M in [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) - the +Active Record is the M in [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an @@ -38,7 +38,7 @@ object on how to write to and read from the database. ### Object Relational Mapping -Object Relational Mapping, commonly referred to as its abbreviation ORM, is +[Object Relational Mapping](https://en.wikipedia.org/wiki/Object-relational_mapping), commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and @@ -304,6 +304,17 @@ user = User.find_by(name: 'David') user.destroy ``` +If you'd like to delete several records in bulk, you may use `destroy_all` +method: + +```ruby +# find and delete all users named David +User.where(name: 'David').destroy_all + +# delete all users +User.destroy_all +``` + Validations ----------- @@ -314,8 +325,8 @@ already in the database, follows a specific format and many more. Validation is a very important issue to consider when persisting to the database, so the methods `save` and `update` take it into account when -running: they return `false` when validation fails and they didn't actually -perform any operation on the database. All of these have a bang counterpart (that +running: they return `false` when validation fails and they don't actually +perform any operations on the database. All of these have a bang counterpart (that is, `save!` and `update!`), which are stricter in that they raise the exception `ActiveRecord::RecordInvalid` if validation fails. A quick example to illustrate: diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 666d987f8c..630dafe632 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -117,6 +117,10 @@ Here is a list with all the available Active Record callbacks, listed in the sam WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed. +NOTE: `before_destroy` callbacks should be placed before `dependent: :destroy` +associations (or use the `prepend: true` option), to ensure they execute before +the records are deleted by `dependent: :destroy`. + ### `after_initialize` and `after_find` The `after_initialize` callback will be called whenever an Active Record object is instantiated, either by directly using `new` or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record `initialize` method. @@ -209,6 +213,7 @@ The following methods trigger callbacks: * `save!` * `save(validate: false)` * `toggle!` +* `touch` * `update_attribute` * `update` * `update!` @@ -241,7 +246,6 @@ Just as with validations, it is also possible to skip callbacks by using the fol * `increment` * `increment_counter` * `toggle` -* `touch` * `update_column` * `update_columns` * `update_all` @@ -254,7 +258,11 @@ Halting Execution As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed. -The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception. +The whole callback chain is wrapped in a transaction. If any callback raises an exception, the execution chain gets halted and a ROLLBACK is issued. To intentionally stop a chain use: + +```ruby +throw :abort +``` WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception. @@ -288,7 +296,7 @@ Article destroyed Conditional Callbacks --------------------- -As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the `:if` and `:unless` options, which can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` option when you want to specify under which conditions the callback **should** be called. If you want to specify the conditions under which the callback **should not** be called, then you may use the `:unless` option. +As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the `:if` and `:unless` options, which can take a symbol, a `Proc` or an `Array`. You may use the `:if` option when you want to specify under which conditions the callback **should** be called. If you want to specify the conditions under which the callback **should not** be called, then you may use the `:unless` option. ### Using `:if` and `:unless` with a `Symbol` @@ -300,16 +308,6 @@ class Order < ApplicationRecord end ``` -### Using `:if` and `:unless` with a String - -You can also use a string that will be evaluated using `eval` and hence needs to contain valid Ruby code. You should use this option only when the string represents a really short condition: - -```ruby -class Order < ApplicationRecord - before_save :normalize_card_number, if: "paid_with_card?" -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: @@ -430,3 +428,32 @@ end ``` WARNING. The `after_commit` and `after_rollback` callbacks are called for all models created, updated, or destroyed within a transaction block. However, if an exception is raised within one of these callbacks, the exception will bubble up and any remaining `after_commit` or `after_rollback` methods will _not_ be executed. As such, if your callback code could raise an exception, you'll need to rescue it and handle it within the callback in order to allow other callbacks to run. + +WARNING. Using both `after_create_commit` and `after_update_commit` in the same model will only allow the last callback defined to take effect, and will override all others. + +```ruby +class User < ApplicationRecord + after_create_commit :log_user_saved_to_db + after_update_commit :log_user_saved_to_db + + private + def log_user_saved_to_db + puts 'User was saved to database' + end +end + +# prints nothing +>> @user = User.create + +# updating @user +>> @user.save +=> User was saved to database +``` + +To register callbacks for both create and update actions, use `after_commit` instead. + +```ruby +class User < ApplicationRecord + after_commit :log_user_saved_to_db, on: [:create, :update] +end +``` diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 6e7e29ed60..ab3af438f5 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -21,7 +21,7 @@ Migration Overview ------------------ Migrations are a convenient way to -[alter your database schema over time](http://en.wikipedia.org/wiki/Schema_migration) +[alter your database schema over time](https://en.wikipedia.org/wiki/Schema_migration) in a consistent and easy way. They use a Ruby DSL so that you don't have to write SQL by hand, allowing your schema and changes to be database independent. @@ -353,8 +353,7 @@ create_table :products, options: "ENGINE=BLACKHOLE" do |t| end ``` -will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table -(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`). +will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table. Also you can pass the `:comment` option with any description for the table that will be stored in database itself and can be viewed with database administration @@ -972,11 +971,11 @@ on. Because this is database-independent, it could be loaded into any database that Active Record supports. This could be very useful if you were to distribute an application that is able to run against multiple databases. -There is however a trade-off: `db/schema.rb` cannot express database specific -items such as triggers, stored procedures or check constraints. While in a -migration you can execute custom SQL statements, the schema dumper cannot -reconstitute those statements from the database. If you are using features like -this, then you should set the schema format to `:sql`. +NOTE: `db/schema.rb` cannot express database specific items such as triggers, +sequences, stored procedures or check constraints, etc. Please note that while +custom SQL statements can be run in migrations, these statements cannot be reconstituted +by the schema dumper. If you are using features like this, then you +should set the schema format to `:sql`. Instead of using Active Record's schema dumper, the database's structure will be dumped using a tool specific to the database (via the `db:structure:dump` diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 6d07291b07..58c61f0864 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -29,8 +29,8 @@ that are supported by the PostgreSQL adapter. ### Bytea -* [type definition](http://www.postgresql.org/docs/current/static/datatype-binary.html) -* [functions and operators](http://www.postgresql.org/docs/current/static/functions-binarystring.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-binary.html) +* [functions and operators](https://www.postgresql.org/docs/current/static/functions-binarystring.html) ```ruby # db/migrate/20140207133952_create_documents.rb @@ -49,8 +49,8 @@ Document.create payload: data ### Array -* [type definition](http://www.postgresql.org/docs/current/static/arrays.html) -* [functions and operators](http://www.postgresql.org/docs/current/static/functions-array.html) +* [type definition](https://www.postgresql.org/docs/current/static/arrays.html) +* [functions and operators](https://www.postgresql.org/docs/current/static/functions-array.html) ```ruby # db/migrate/20140207133952_create_books.rb @@ -83,8 +83,8 @@ Book.where("array_length(ratings, 1) >= 3") ### Hstore -* [type definition](http://www.postgresql.org/docs/current/static/hstore.html) -* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN167712) +* [type definition](https://www.postgresql.org/docs/current/static/hstore.html) +* [functions and operators](https://www.postgresql.org/docs/current/static/hstore.html#AEN179902) NOTE: You need to enable the `hstore` extension to use hstore. @@ -114,16 +114,21 @@ Profile.where("settings->'color' = ?", "yellow") # => #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]> ``` -### JSON +### JSON and JSONB -* [type definition](http://www.postgresql.org/docs/current/static/datatype-json.html) -* [functions and operators](http://www.postgresql.org/docs/current/static/functions-json.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-json.html) +* [functions and operators](https://www.postgresql.org/docs/current/static/functions-json.html) ```ruby # db/migrate/20131220144913_create_events.rb +# ... for json datatype: create_table :events do |t| t.json 'payload' end +# ... or for jsonb datatype: +create_table :events do |t| + t.jsonb 'payload' +end # app/models/event.rb class Event < ApplicationRecord @@ -142,8 +147,8 @@ Event.where("payload->>'kind' = ?", "user_renamed") ### Range Types -* [type definition](http://www.postgresql.org/docs/current/static/rangetypes.html) -* [functions and operators](http://www.postgresql.org/docs/current/static/functions-range.html) +* [type definition](https://www.postgresql.org/docs/current/static/rangetypes.html) +* [functions and operators](https://www.postgresql.org/docs/current/static/functions-range.html) This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.2.2/Range.html) objects. @@ -177,7 +182,7 @@ event.ends_at # => Thu, 13 Feb 2014 ### Composite Types -* [type definition](http://www.postgresql.org/docs/current/static/rowtypes.html) +* [type definition](https://www.postgresql.org/docs/current/static/rowtypes.html) Currently there is no special support for composite types. They are mapped to normal text columns: @@ -217,7 +222,7 @@ contact.save! ### Enumerated Types -* [type definition](http://www.postgresql.org/docs/current/static/datatype-enum.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-enum.html) Currently there is no special support for enumerated types. They are mapped as normal text columns: @@ -255,7 +260,7 @@ article.status = "published" article.save! ``` -To add a new value before/after existing one you should use [ALTER TYPE](http://www.postgresql.org/docs/current/static/sql-altertype.html): +To add a new value before/after existing one you should use [ALTER TYPE](https://www.postgresql.org/docs/current/static/sql-altertype.html): ```ruby # db/migrate/20150720144913_add_new_state_to_articles.rb @@ -269,7 +274,7 @@ def up end ``` -NOTE: ENUM values can't be dropped currently. You can read why [here](http://www.postgresql.org/message-id/29F36C7C98AB09499B1A209D48EAA615B7653DBC8A@mail2a.alliedtesting.com). +NOTE: ENUM values can't be dropped currently. You can read why [here](https://www.postgresql.org/message-id/29F36C7C98AB09499B1A209D48EAA615B7653DBC8A@mail2a.alliedtesting.com). Hint: to show all the values of the all enums you have, you should call this query in `bin/rails db` or `psql` console: @@ -284,9 +289,9 @@ SELECT n.nspname AS enum_schema, ### UUID -* [type definition](http://www.postgresql.org/docs/current/static/datatype-uuid.html) -* [pgcrypto generator function](http://www.postgresql.org/docs/current/static/pgcrypto.html#AEN159361) -* [uuid-ossp generator functions](http://www.postgresql.org/docs/current/static/uuid-ossp.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-uuid.html) +* [pgcrypto generator function](https://www.postgresql.org/docs/current/static/pgcrypto.html#AEN182570) +* [uuid-ossp generator functions](https://www.postgresql.org/docs/current/static/uuid-ossp.html) NOTE: You need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp` extension to use uuid. @@ -335,8 +340,8 @@ See [this section](#uuid-primary-keys) for more details on using UUIDs as primar ### Bit String Types -* [type definition](http://www.postgresql.org/docs/current/static/datatype-bit.html) -* [functions and operators](http://www.postgresql.org/docs/current/static/functions-bitstring.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-bit.html) +* [functions and operators](https://www.postgresql.org/docs/current/static/functions-bitstring.html) ```ruby # db/migrate/20131220144913_create_users.rb @@ -359,7 +364,7 @@ user.save! ### Network Address Types -* [type definition](http://www.postgresql.org/docs/current/static/datatype-net-types.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-net-types.html) The types `inet` and `cidr` are mapped to Ruby [`IPAddr`](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/ipaddr/rdoc/IPAddr.html) @@ -394,7 +399,7 @@ macbook.address ### Geometric Types -* [type definition](http://www.postgresql.org/docs/current/static/datatype-geometric.html) +* [type definition](https://www.postgresql.org/docs/current/static/datatype-geometric.html) All geometric types, with the exception of `points` are mapped to normal text. A point is casted to an array containing `x` and `y` coordinates. @@ -452,7 +457,7 @@ Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)", Database Views -------------- -* [view creation](http://www.postgresql.org/docs/current/static/sql-createview.html) +* [view creation](https://www.postgresql.org/docs/current/static/sql-createview.html) Imagine you need to work with a legacy database containing the following table: diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 31220f9be2..4e28e31a53 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -118,7 +118,7 @@ You can also use this method to query for multiple objects. Call the `find` meth ```ruby # Find the clients with primary keys 1 and 10. -client = Client.find([1, 10]) # Or even Client.find(1, 10) +clients = Client.find([1, 10]) # Or even Client.find(1, 10) # => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">] ``` @@ -150,7 +150,7 @@ The `take` method returns `nil` if no record is found and no exception will be r You can pass in a numerical argument to the `take` method to return up to that number of results. For example ```ruby -client = Client.take(2) +clients = Client.take(2) # => [ # #<Client id: 1, first_name: "Lifo">, # #<Client id: 220, first_name: "Sara"> @@ -189,7 +189,7 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co You can pass in a numerical argument to the `first` method to return up to that number of results. For example ```ruby -client = Client.first(3) +clients = Client.first(3) # => [ # #<Client id: 1, first_name: "Lifo">, # #<Client id: 2, first_name: "Fifo">, @@ -240,7 +240,7 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co You can pass in a numerical argument to the `last` method to return up to that number of results. For example ```ruby -client = Client.last(3) +clients = Client.last(3) # => [ # #<Client id: 219, first_name: "James">, # #<Client id: 220, first_name: "Sara">, @@ -414,7 +414,7 @@ end `find_in_batches` works on model classes, as seen above, and also on relations: ```ruby -Invoice.pending.find_in_batches do |invoice| +Invoice.pending.find_in_batches do |invoices| pending_invoices_export.add_invoices(invoices) end ``` @@ -513,8 +513,6 @@ Article.where(author: author) Author.joins(:articles).where(articles: { author: author }) ``` -NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`. - #### Range Conditions ```ruby @@ -557,6 +555,19 @@ In other words, this query can be generated by calling `where` with no argument, SELECT * FROM clients WHERE (clients.locked != 1) ``` +### OR Conditions + +`OR` conditions between two relations can be built by calling `or` on the first +relation, and passing the second one as an argument. + +```ruby +Client.where(locked: true).or(Client.where(orders_count: [1,3,5])) +``` + +```sql +SELECT * FROM clients WHERE (clients.locked = 1 OR clients.orders_count IN (1,3,5)) +``` + Ordering -------- @@ -790,7 +801,7 @@ The SQL that would be executed: SELECT * FROM articles WHERE id > 10 ORDER BY id DESC # Original query without `only` -SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20 +SELECT * FROM articles WHERE id > 10 ORDER BY id DESC LIMIT 20 ``` @@ -809,14 +820,14 @@ Article.find(10).comments.reorder('name') The SQL that would be executed: ```sql -SELECT * FROM articles WHERE id = 10 +SELECT * FROM articles WHERE id = 10 LIMIT 1 SELECT * FROM comments WHERE article_id = 10 ORDER BY name ``` In the case where the `reorder` clause is not used, the SQL executed would be: ```sql -SELECT * FROM articles WHERE id = 10 +SELECT * FROM articles WHERE id = 10 LIMIT 1 SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC ``` @@ -953,9 +964,6 @@ class Client < ApplicationRecord end ``` -NOTE: Please note that the optimistic locking will be ignored if you update the -locking column's value. - ### Pessimistic Locking Pessimistic locking uses a locking mechanism provided by the underlying database. Using `lock` when building a relation obtains an exclusive lock on the selected rows. Relations using `lock` are usually wrapped inside a transaction for preventing deadlock conditions. @@ -1083,7 +1091,7 @@ This produces: ```sql SELECT articles.* FROM articles - INNER JOIN categories ON articles.category_id = categories.id + INNER JOIN categories ON categories.id = articles.category_id INNER JOIN comments ON comments.article_id = articles.id ``` @@ -1384,8 +1392,9 @@ class Client < ApplicationRecord end ``` -NOTE: The `default_scope` is also applied while creating/building a record. -It is not applied while updating a record. E.g.: +NOTE: The `default_scope` is also applied while creating/building a record +when the scope arguments are given as a `Hash`. It is not applied while +updating a record. E.g.: ```ruby class Client < ApplicationRecord @@ -1396,6 +1405,17 @@ Client.new # => #<Client id: nil, active: true> Client.unscoped.new # => #<Client id: nil, active: nil> ``` +Be aware that, when given in the `Array` format, `default_scope` query arguments +cannot be converted to a `Hash` for default attribute assignment. E.g.: + +```ruby +class Client < ApplicationRecord + default_scope { where("active = ?", true) } +end + +Client.new # => #<Client id: nil, active: nil> +``` + ### Merging of scopes Just like `where` clauses scopes are merged using `AND` conditions. @@ -1519,7 +1539,7 @@ Read the full documentation about enums Understanding The Method Chaining --------------------------------- -The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining), +The Active Record pattern implements [Method Chaining](https://en.wikipedia.org/wiki/Method_chaining), which allow us to use multiple Active Record methods together in a simple and straightforward way. You can chain methods in a statement when the previous method called returns an @@ -1547,7 +1567,7 @@ SELECT people.id, people.name, comments.text FROM people INNER JOIN comments ON comments.person_id = people.id -WHERE comments.created_at = '2015-01-01' +WHERE comments.created_at > '2015-01-01' ``` ### Retrieving specific data from multiple tables @@ -1692,10 +1712,10 @@ Client.find_by_sql("SELECT * FROM clients ### `select_all` -`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record. +`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. This method will return an instance of `ActiveRecord::Result` class and calling `to_hash` on this object would return you an array of hashes where each hash indicates a record. ```ruby -Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") +Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_hash # => [ # {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, # {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} @@ -1851,14 +1871,14 @@ All calculation methods work directly on a model: ```ruby Client.count -# SELECT count(*) AS count_all FROM clients +# SELECT COUNT(*) FROM clients ``` Or on a relation: ```ruby Client.where(first_name: 'Ryan').count -# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') +# SELECT COUNT(*) FROM clients WHERE (first_name = 'Ryan') ``` You can also use various finder methods on a relation for performing complex calculations: @@ -1870,9 +1890,9 @@ Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received' Which will execute: ```sql -SELECT count(DISTINCT clients.id) AS count_all FROM clients - LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE - (clients.first_name = 'Ryan' AND orders.status = 'received') +SELECT COUNT(DISTINCT clients.id) FROM clients + LEFT OUTER JOIN orders ON orders.client_id = clients.id + WHERE (clients.first_name = 'Ryan' AND orders.status = 'received') ``` ### Count @@ -2025,4 +2045,4 @@ following pointers may be helpful: * MariaDB: [EXPLAIN](https://mariadb.com/kb/en/mariadb/explain/) -* PostgreSQL: [Using EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html) +* PostgreSQL: [Using EXPLAIN](https://www.postgresql.org/docs/current/static/using-explain.html) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 665e97c470..e9157f3db1 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -490,9 +490,6 @@ If you set `:only_integer` to `true`, then it will use the regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using `Float`. -WARNING. Note that the regular expression above allows a trailing newline -character. - ```ruby class Player < ApplicationRecord validates :points, numericality: true @@ -641,7 +638,7 @@ class Holiday < ApplicationRecord message: "should happen once per year" } end ``` -Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. +Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](https://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. There is also a `:case_sensitive` option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to @@ -895,7 +892,7 @@ Conditional Validation Sometimes it will make sense to validate an object only when a given predicate is satisfied. You can do that by using the `:if` and `:unless` options, which -can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` +can take a symbol, a `Proc` or an `Array`. You may use the `:if` option when you want to specify when the validation **should** happen. If you want to specify when the validation **should not** happen, then you may use the `:unless` option. @@ -916,18 +913,6 @@ class Order < ApplicationRecord end ``` -### Using a String with `:if` and `:unless` - -You can also use a string that will be evaluated using `eval` and needs to -contain valid Ruby code. You should use this option only when the string -represents a really short condition. - -```ruby -class Person < ApplicationRecord - validates :surname, presence: true, if: "name.nil?" -end -``` - ### Using a Proc with `:if` and `:unless` Finally, it's possible to associate `:if` and `:unless` with a `Proc` object @@ -968,7 +953,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and ```ruby class Computer < ApplicationRecord validates :mouse, presence: true, - if: ["market.retail?", :desktop?], + if: [Proc.new { |c| c.market.retail? }, :desktop?], unless: Proc.new { |c| c.trackpad.present? } end ``` diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md new file mode 100644 index 0000000000..ec90e44358 --- /dev/null +++ b/guides/source/active_storage_overview.md @@ -0,0 +1,559 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Active Storage Overview +======================= + +This guide covers how to attach files to your Active Record models. + +After reading this guide, you will know: + +* How to attach one or many files to a record. +* How to delete an attached file. +* How to link to an attached file. +* How to use variants to transform images. +* How to generate an image representation of a non-image file, such as a PDF or a video. +* How to send file uploads directly from browsers to a storage service, + bypassing your application servers. +* How to clean up files stored during testing. +* How to implement support for additional storage services. + +-------------------------------------------------------------------------------- + +What is Active Storage? +----------------------- + +Active Storage facilitates uploading files to a cloud storage service like +Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those +files to Active Record objects. It comes with a local disk-based service for +development and testing and supports mirroring files to subordinate services for +backups and migrations. + +Using Active Storage, an application can transform image uploads with +[ImageMagick](https://www.imagemagick.org), generate image representations of +non-image uploads like PDFs and videos, and extract metadata from arbitrary +files. + +## Setup + +Active Storage uses two tables in your application’s database named +`active_storage_blobs` and `active_storage_attachments`. After upgrading your +application to Rails 5.2, run `rails active_storage:install` to generate a +migration that creates these tables. Use `rails db:migrate` to run the +migration. + +You need not run `rails active_storage:install` in a new Rails 5.2 application: +the migration is generated automatically. + +Declare Active Storage services in `config/storage.yml`. For each service your +application uses, provide a name and the requisite configuration. The example +below declares three services named `local`, `test`, and `amazon`: + +```yaml +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +amazon: + service: S3 + access_key_id: "" + secret_access_key: "" +``` + +Tell Active Storage which service to use by setting +`Rails.application.config.active_storage.service`. Because each environment will +likely use a different service, it is recommended to do this on a +per-environment basis. To use the disk service from the previous example in the +development environment, you would add the following to +`config/environments/development.rb`: + +```ruby +# Store files locally. +config.active_storage.service = :local +``` + +To use the Amazon S3 service in production, you add the following to +`config/environments/production.rb`: + +```ruby +# Store files on Amazon S3. +config.active_storage.service = :amazon +``` + +Continue reading for more information on the built-in service adapters (e.g. +`Disk` and `S3`) and the configuration they require. + +### Disk Service + +Declare a Disk service in `config/storage.yml`: + +```yaml +local: + service: Disk + root: <%= Rails.root.join("storage") %> +``` + +### Amazon S3 Service + +Declare an S3 service in `config/storage.yml`: + +```yaml +amazon: + service: S3 + access_key_id: "" + secret_access_key: "" + region: "" + bucket: "" +``` + +Add the [`aws-sdk-s3`](https://github.com/aws/aws-sdk-ruby) gem to your `Gemfile`: + +```ruby +gem "aws-sdk-s3", require: false +``` + +### Microsoft Azure Storage Service + +Declare an Azure Storage service in `config/storage.yml`: + +```yaml +azure: + service: AzureStorage + path: "" + storage_account_name: "" + storage_access_key: "" + container: "" +``` + +Add the [`azure-storage`](https://github.com/Azure/azure-storage-ruby) gem to your `Gemfile`: + +```ruby +gem "azure-storage", require: false +``` + +### Google Cloud Storage Service + +Declare a Google Cloud Storage service in `config/storage.yml`: + +```yaml +google: + service: GCS + credentials: <%= Rails.root.join("path/to/keyfile.json") %> + project: "" + bucket: "" +``` + +Optionally provide a Hash of credentials instead of a keyfile path: + +```yaml +google: + service: GCS + credentials: + type: "service_account" + project_id: "" + private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %> + private_key: <%= Rails.application.credentials.dig(:gcs, :private_key) %> + client_email: "" + client_id: "" + auth_uri: "https://accounts.google.com/o/oauth2/auth" + token_uri: "https://accounts.google.com/o/oauth2/token" + auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs" + client_x509_cert_url: "" + project: "" + bucket: "" +``` + +Add the [`google-cloud-storage`](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-storage) gem to your `Gemfile`: + +```ruby +gem "google-cloud-storage", "~> 1.3", require: false +``` + +### Mirror Service + +You can keep multiple services in sync by defining a mirror service. When a file +is uploaded or deleted, it's done across all the mirrored services. Mirrored +services can be used to facilitate a migration between services in production. +You can start mirroring to the new service, copy existing files from the old +service to the new, then go all-in on the new service. Define each of the +services you'd like to use as described above and reference them from a mirrored +service. + +```yaml +s3_west_coast: + service: S3 + access_key_id: "" + secret_access_key: "" + region: "" + bucket: "" + +s3_east_coast: + service: S3 + access_key_id: "" + secret_access_key: "" + region: "" + bucket: "" + +production: + service: Mirror + primary: s3_east_coast + mirrors: + - s3_west_coast +``` + +NOTE: Files are served from the primary service. + +Attaching Files to Records +-------------------------- + +### `has_one_attached` + +The `has_one_attached` macro sets up a one-to-one mapping between records and +files. Each record can have one file attached to it. + +For example, suppose your application has a `User` model. If you want each user to +have an avatar, define the `User` model like this: + +```ruby +class User < ApplicationRecord + has_one_attached :avatar +end +``` + +You can create a user with an avatar: + +```ruby +class SignupController < ApplicationController + def create + user = User.create!(user_params) + session[:user_id] = user.id + redirect_to root_path + end + + private + def user_params + params.require(:user).permit(:email_address, :password, :avatar) + end +end +``` + +Call `avatar.attach` to attach an avatar to an existing user: + +```ruby +Current.user.avatar.attach(params[:avatar]) +``` + +Call `avatar.attached?` to determine whether a particular user has an avatar: + +```ruby +Current.user.avatar.attached? +``` + +### `has_many_attached` + +The `has_many_attached` macro sets up a one-to-many relationship between records +and files. Each record can have many files attached to it. + +For example, suppose your application has a `Message` model. If you want each +message to have many images, define the `Message` model like this: + +```ruby +class Message < ApplicationRecord + has_many_attached :images +end +``` + +You can create a message with images: + +```ruby +class MessagesController < ApplicationController + def create + message = Message.create!(message_params) + redirect_to message + end + + private + def message_params + params.require(:message).permit(:title, :content, images: []) + end +end +``` + +Call `images.attach` to add new images to an existing message: + +```ruby +@message.images.attach(params[:images]) +``` + +Call `images.attached?` to determine whether a particular message has any images: + +```ruby +@message.images.attached? +``` + +Removing Files +-------------- + +To remove an attachment from a model, call `purge` on the attachment. Removal +can be done in the background if your application is setup to use Active Job. +Purging deletes the blob and the file from the storage service. + +```ruby +# Synchronously destroy the avatar and actual resource files. +user.avatar.purge + +# Destroy the associated models and actual resource files async, via Active Job. +user.avatar.purge_later +``` + +Linking to Files +---------------- + +Generate a permanent URL for the blob that points to the application. Upon +access, a redirect to the actual service endpoint is returned. This indirection +decouples the public URL from the actual one, and allows, for example, mirroring +attachments in different services for high-availability. The redirection has an +HTTP expiration of 5 min. + +```ruby +url_for(user.avatar) +``` + +To create a download link, use the `rails_blob_{path|url}` helper. Using this +helper allows you to set the disposition. + +```ruby +rails_blob_path(user.avatar, disposition: "attachment") +``` + +Transforming Images +------------------- + +To create variation of the image, call `variant` on the Blob. +You can pass any [MiniMagick](https://github.com/minimagick/minimagick) +supported transformation to the method. + +To enable variants, add `mini_magick` to your `Gemfile`: + +```ruby +gem 'mini_magick' +``` + +When the browser hits the variant URL, Active Storage will lazy transform the +original blob into the format you specified and redirect to its new service +location. + +```erb +<%= image_tag user.avatar.variant(resize: "100x100") %> +``` + +Previewing Files +---------------- + +Some non-image files can be previewed: that is, they can be presented as images. +For example, a video file can be previewed by extracting its first frame. Out of +the box, Active Storage supports previewing videos and PDF documents. + +```erb +<ul> + <% @message.files.each do |file| %> + <li> + <%= image_tag file.preview(resize: "100x100>") %> + </li> + <% end %> +</ul> +``` + +WARNING: Extracting previews requires third-party applications, `ffmpeg` for +video and `mutool` for PDFs. These libraries are not provided by Rails. You must +install them yourself to use the built-in previewers. Before you install and use +third-party software, make sure you understand the licensing implications of +doing so. + +Direct Uploads +-------------- + +Active Storage, with its included JavaScript library, supports uploading +directly from the client to the cloud. + +### Direct upload installation + +1. Include `activestorage.js` in your application's JavaScript bundle. + + Using the asset pipeline: + + ```js + //= require activestorage + + ``` + + Using the npm package: + + ```js + import * as ActiveStorage from "activestorage" + ActiveStorage.start() + ``` + +2. Annotate file inputs with the direct upload URL. + + ```ruby + <%= form.file_field :attachments, multiple: true, direct_upload: true %> + ``` +3. That's it! Uploads begin upon form submission. + +### Direct upload JavaScript events + +| Event name | Event target | Event data (`event.detail`) | Description | +| --- | --- | --- | --- | +| `direct-uploads:start` | `<form>` | None | A form containing files for direct upload fields was submitted. | +| `direct-upload:initialize` | `<input>` | `{id, file}` | Dispatched for every file after form submission. | +| `direct-upload:start` | `<input>` | `{id, file}` | A direct upload is starting. | +| `direct-upload:before-blob-request` | `<input>` | `{id, file, xhr}` | Before making a request to your application for direct upload metadata. | +| `direct-upload:before-storage-request` | `<input>` | `{id, file, xhr}` | Before making a request to store a file. | +| `direct-upload:progress` | `<input>` | `{id, file, progress}` | As requests to store files progress. | +| `direct-upload:error` | `<input>` | `{id, file, error}` | An error occurred. An `alert` will display unless this event is canceled. | +| `direct-upload:end` | `<input>` | `{id, file}` | A direct upload has ended. | +| `direct-uploads:end` | `<form>` | None | All direct uploads have ended. | + +### Example + +You can use these events to show the progress of an upload. + +![direct-uploads](https://user-images.githubusercontent.com/5355/28694528-16e69d0c-72f8-11e7-91a7-c0b8cfc90391.gif) + +To show the uploaded files in a form: + +```js +// direct_uploads.js + +addEventListener("direct-upload:initialize", event => { + const { target, detail } = event + const { id, file } = detail + target.insertAdjacentHTML("beforebegin", ` + <div id="direct-upload-${id}" class="direct-upload direct-upload--pending"> + <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div> + <span class="direct-upload__filename">${file.name}</span> + </div> + `) +}) + +addEventListener("direct-upload:start", event => { + const { id } = event.detail + const element = document.getElementById(`direct-upload-${id}`) + element.classList.remove("direct-upload--pending") +}) + +addEventListener("direct-upload:progress", event => { + const { id, progress } = event.detail + const progressElement = document.getElementById(`direct-upload-progress-${id}`) + progressElement.style.width = `${progress}%` +}) + +addEventListener("direct-upload:error", event => { + event.preventDefault() + const { id, error } = event.detail + const element = document.getElementById(`direct-upload-${id}`) + element.classList.add("direct-upload--error") + element.setAttribute("title", error) +}) + +addEventListener("direct-upload:end", event => { + const { id } = event.detail + const element = document.getElementById(`direct-upload-${id}`) + element.classList.add("direct-upload--complete") +}) +``` + +Add styles: + +```css +/* direct_uploads.css */ + +.direct-upload { + display: inline-block; + position: relative; + padding: 2px 4px; + margin: 0 3px 3px 0; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 3px; + font-size: 11px; + line-height: 13px; +} + +.direct-upload--pending { + opacity: 0.6; +} + +.direct-upload__progress { + position: absolute; + top: 0; + left: 0; + bottom: 0; + opacity: 0.2; + background: #0076ff; + transition: width 120ms ease-out, opacity 60ms 60ms ease-in; + transform: translate3d(0, 0, 0); +} + +.direct-upload--complete .direct-upload__progress { + opacity: 0.4; +} + +.direct-upload--error { + border-color: red; +} + +input[type=file][data-direct-upload-url][disabled] { + display: none; +} +``` + +Discarding Files Stored During System Tests +------------------------------------------- + +System tests clean up test data by rolling back a transaction. Because destroy +is never called on an object, the attached files are never cleaned up. If you +want to clear the files, you can do it in an `after_teardown` callback. Doing it +here ensures that all connections created during the test are complete and +you won't receive an error from Active Storage saying it can't find a file. + +```ruby +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] + + def remove_uploaded_files + FileUtils.rm_rf("#{Rails.root}/storage_test") + end + + def after_teardown + super + remove_uploaded_files + end +end +``` + +If your system tests verify the deletion of a model with attachments and you're +using Active Job, set your test environment to use the inline queue adapter so +the purge job is executed immediately rather at an unknown time in the future. + +You may also want to use a separate service definition for the test environment +so your tests don't delete the files you create during development. + +```ruby +# Use inline job processing to make things happen immediately +config.active_job.queue_adapter = :inline + +# Separate file storage in the test environment +config.active_storage.service = :local_test +``` + +Implementing Support for Other Cloud Services +--------------------------------------------- + +If you need to support a cloud service other than these, you will need to +implement the Service. Each service extends +[`ActiveStorage::Service`](https://github.com/rails/rails/blob/master/activestorage/lib/active_storage/service.rb) +by implementing the methods necessary to upload and download files to the cloud. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 67bed4c8da..8e2826bb85 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -135,9 +135,9 @@ NOTE: Defined in `active_support/core_ext/object/blank.rb`. ### `duplicable?` -In Ruby 2.4 most objects can be duplicated via `dup` or `clone` except +In Ruby 2.4 most objects can be duplicated via `dup` or `clone` except methods and certain numbers. Though Ruby 2.2 and 2.3 can't duplicate `nil`, -`false`, `true`, and symbols as well as instances `Float`, `Fixnum`, +`false`, `true`, and symbols as well as instances `Float`, `Fixnum`, and `Bignum` instances. ```ruby @@ -634,7 +634,7 @@ NOTE: Defined in `active_support/core_ext/module/introspection.rb`. #### `parent_name` -The `parent_name` method on a nested named module returns the fully-qualified name of the module that contains its corresponding constant: +The `parent_name` method on a nested named module returns the fully qualified name of the module that contains its corresponding constant: ```ruby module X @@ -674,44 +674,6 @@ M.parents # => [X::Y, X, Object] NOTE: Defined in `active_support/core_ext/module/introspection.rb`. -### Reachable - -A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant. - -That is what ordinarily happens, if a module is called "M", the `M` constant exists and holds it: - -```ruby -module M -end - -M.reachable? # => true -``` - -But since constants and modules are indeed kind of decoupled, module objects can become unreachable: - -```ruby -module M -end - -orphan = Object.send(:remove_const, :M) - -# The module object is orphan now but it still has a name. -orphan.name # => "M" - -# You cannot reach it via the constant M because it does not even exist. -orphan.reachable? # => false - -# Let's define a module called "M" again. -module M -end - -# The constant M exists now again, and it stores a module -# object called "M", but it is a new instance. -orphan.reachable? # => false -``` - -NOTE: Defined in `active_support/core_ext/module/reachable.rb`. - ### Anonymous A module may or may not have a name: @@ -745,7 +707,6 @@ end m = Object.send(:remove_const, :M) -m.reachable? # => false m.anonymous? # => false ``` @@ -755,6 +716,8 @@ NOTE: Defined in `active_support/core_ext/module/anonymous.rb`. ### Method Delegation +#### `delegate` + The macro `delegate` offers an easy way to forward methods. Let's imagine that users in some application have login information in the `User` model but name and other data in a separate `Profile` model: @@ -837,13 +800,36 @@ In the previous example the macro generates `avatar_size` rather than `size`. NOTE: Defined in `active_support/core_ext/module/delegation.rb` +#### `delegate_missing_to` + +Imagine you would like to delegate everything missing from the `User` object, +to the `Profile` one. The `delegate_missing_to` macro lets you implement this +in a breeze: + +```ruby +class User < ApplicationRecord + has_one :profile + + delegate_missing_to :profile +end +``` + +The target can be anything callable within the object, e.g. instance variables, +methods, constants, etc. Only the public methods of the target are delegated. + +NOTE: Defined in `active_support/core_ext/module/delegation.rb`. + ### Redefining Methods There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either. The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. -NOTE: Defined in `active_support/core_ext/module/remove_method.rb` +You can also use `silence_redefinition_of_method` if you need to define +the replacement method yourself (because you're using `delegate`, for +example). + +NOTE: Defined in `active_support/core_ext/module/redefine_method.rb`. Extensions to `Class` --------------------- @@ -906,8 +892,7 @@ The generation of the writer instance method can be prevented by setting the opt ```ruby module ActiveRecord class Base - class_attribute :table_name_prefix, instance_writer: false - self.table_name_prefix = "" + class_attribute :table_name_prefix, instance_writer: false, default: "my" end end ``` @@ -931,7 +916,7 @@ When `:instance_reader` is `false`, the instance predicate returns a `NoMethodEr If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. -NOTE: Defined in `active_support/core_ext/class/attribute.rb` +NOTE: Defined in `active_support/core_ext/class/attribute.rb`. #### `cattr_reader`, `cattr_writer`, and `cattr_accessor` @@ -940,8 +925,7 @@ The macros `cattr_reader`, `cattr_writer`, and `cattr_accessor` are analogous to ```ruby class MysqlAdapter < AbstractAdapter # Generates class methods to access @@emulate_booleans. - cattr_accessor :emulate_booleans - self.emulate_booleans = true + cattr_accessor :emulate_booleans, default: true end ``` @@ -950,8 +934,7 @@ Instance methods are created as well for convenience, they are just proxies to t ```ruby module ActionView class Base - cattr_accessor :field_error_proc - @@field_error_proc = Proc.new{ ... } + cattr_accessor :field_error_proc, default: Proc.new { ... } end end ``` @@ -963,7 +946,7 @@ Also, you can pass a block to `cattr_*` to set up the attribute with a default v ```ruby class MysqlAdapter < AbstractAdapter # Generates class methods to access @@emulate_booleans with default value of true. - cattr_accessor(:emulate_booleans) { true } + cattr_accessor :emulate_booleans, default: true end ``` @@ -1691,7 +1674,7 @@ Specifically performs these transformations: * Capitalizes the first word. The capitalization of the first word can be turned off by setting the -+:capitalize+ option to false (default is true). +`:capitalize` option to false (default is true). ```ruby "name".humanize # => "Name" @@ -1769,7 +1752,7 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr "2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200 ``` -Default is `:utc`. +Default is `:local`. Please refer to the documentation of `Date._parse` for further details. @@ -1813,7 +1796,7 @@ NOTE: Defined in `active_support/core_ext/numeric/bytes.rb`. ### Time -Enables the use of time calculations and declarations, like `45.minutes + 2.hours + 4.years`. +Enables the use of time calculations and declarations, like `45.minutes + 2.hours + 4.weeks`. These methods use Time#advance for precise date calculations when using from_now, ago, etc. as well as adding or subtracting their results from a Time object. For example: @@ -1822,14 +1805,16 @@ as well as adding or subtracting their results from a Time object. For example: # equivalent to Time.current.advance(months: 1) 1.month.from_now -# equivalent to Time.current.advance(years: 2) -2.years.from_now +# equivalent to Time.current.advance(weeks: 2) +2.weeks.from_now -# equivalent to Time.current.advance(months: 4, years: 5) -(4.months + 5.years).from_now +# equivalent to Time.current.advance(months: 4, weeks: 5) +(4.months + 5.weeks).from_now ``` -NOTE: Defined in `active_support/core_ext/numeric/time.rb` +WARNING. For other durations please refer to the time extensions to `Integer`. + +NOTE: Defined in `active_support/core_ext/numeric/time.rb`. ### Formatting @@ -1964,6 +1949,28 @@ The method `ordinalize` returns the ordinal string corresponding to the receiver NOTE: Defined in `active_support/core_ext/integer/inflections.rb`. +### Time + +Enables the use of time calculations and declarations, like `4.months + 5.years`. + +These methods use Time#advance for precise date calculations when using from_now, ago, etc. +as well as adding or subtracting their results from a Time object. For example: + +```ruby +# equivalent to Time.current.advance(months: 1) +1.month.from_now + +# equivalent to Time.current.advance(years: 2) +2.years.from_now + +# equivalent to Time.current.advance(months: 4, years: 5) +(4.months + 5.years).from_now +``` + +WARNING. For other durations please refer to the time extensions to `Numeric`. + +NOTE: Defined in `active_support/core_ext/integer/time.rb`. + Extensions to `BigDecimal` -------------------------- ### `to_s` @@ -1971,19 +1978,19 @@ Extensions to `BigDecimal` The method `to_s` provides a default specifier of "F". This means that a simple call to `to_s` will result in floating point representation instead of engineering notation: ```ruby -BigDecimal.new(5.00, 6).to_s # => "5.0" +BigDecimal(5.00, 6).to_s # => "5.0" ``` and that symbol specifiers are also supported: ```ruby -BigDecimal.new(5.00, 6).to_s(:db) # => "5.0" +BigDecimal(5.00, 6).to_s(:db) # => "5.0" ``` Engineering notation is still supported: ```ruby -BigDecimal.new(5.00, 6).to_s("e") # => "0.5E1" +BigDecimal(5.00, 6).to_s("e") # => "0.5E1" ``` Extensions to `Enumerable` @@ -2341,7 +2348,7 @@ This method is similar in purpose to `Kernel#Array`, but there are some differen * If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns an array with the argument as its single element right away. * If the returned value from `to_ary` is neither `nil` nor an `Array` object, `Kernel#Array` raises an exception, while `Array.wrap` does not, it just returns the value. -* It does not call `to_a` on the argument, if the argument does not respond to +to_ary+ it returns an array with the argument as its single element. +* It does not call `to_a` on the argument, if the argument does not respond to `to_ary` it returns an array with the argument as its single element. The last point is particularly worth comparing for some enumerables: @@ -2963,6 +2970,32 @@ Extensions to `Date` NOTE: All the following methods are defined in `active_support/core_ext/date/calculations.rb`. +```ruby +yesterday +tomorrow +beginning_of_week (at_beginning_of_week) +end_of_week (at_end_of_week) +monday +sunday +weeks_ago +prev_week (last_week) +next_week +months_ago +months_since +beginning_of_month (at_beginning_of_month) +end_of_month (at_end_of_month) +last_month +beginning_of_quarter (at_beginning_of_quarter) +end_of_quarter (at_end_of_quarter) +beginning_of_year (at_beginning_of_year) +end_of_year (at_end_of_year) +years_ago +years_since +last_year +on_weekday? +on_weekend? +``` + INFO: The following calculation methods have edge cases in October 1582, since days 5..14 just do not exist. This guide does not document their behavior around those days for brevity, but it is enough to say that they do what you would expect. That is, `Date.new(1582, 10, 4).tomorrow` returns `Date.new(1582, 10, 15)` and so on. Please check `test/core_ext/date_ext_test.rb` in the Active Support test suite for expected behavior. #### `Date.current` @@ -2973,68 +3006,6 @@ When making Date comparisons using methods which honor the user time zone, make #### Named dates -##### `prev_year`, `next_year` - -In Ruby 1.9 `prev_year` and `next_year` return a date with the same day/month in the last or next year: - -```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_year # => Fri, 08 May 2009 -d.next_year # => Sun, 08 May 2011 -``` - -If date is the 29th of February of a leap year, you obtain the 28th: - -```ruby -d = Date.new(2000, 2, 29) # => Tue, 29 Feb 2000 -d.prev_year # => Sun, 28 Feb 1999 -d.next_year # => Wed, 28 Feb 2001 -``` - -`prev_year` is aliased to `last_year`. - -##### `prev_month`, `next_month` - -In Ruby 1.9 `prev_month` and `next_month` return the date with the same day in the last or next month: - -```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_month # => Thu, 08 Apr 2010 -d.next_month # => Tue, 08 Jun 2010 -``` - -If such a day does not exist, the last day of the corresponding month is returned: - -```ruby -Date.new(2000, 5, 31).prev_month # => Sun, 30 Apr 2000 -Date.new(2000, 3, 31).prev_month # => Tue, 29 Feb 2000 -Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000 -Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000 -``` - -`prev_month` is aliased to `last_month`. - -##### `prev_quarter`, `next_quarter` - -Same as `prev_month` and `next_month`. It returns the date with the same day in the previous or next quarter: - -```ruby -t = Time.local(2010, 5, 8) # => Sat, 08 May 2010 -t.prev_quarter # => Mon, 08 Feb 2010 -t.next_quarter # => Sun, 08 Aug 2010 -``` - -If such a day does not exist, the last day of the corresponding month is returned: - -```ruby -Time.local(2000, 7, 31).prev_quarter # => Sun, 30 Apr 2000 -Time.local(2000, 5, 31).prev_quarter # => Tue, 29 Feb 2000 -Time.local(2000, 10, 31).prev_quarter # => Mon, 30 Oct 2000 -Time.local(2000, 11, 31).next_quarter # => Wed, 28 Feb 2001 -``` - -`prev_quarter` is aliased to `last_quarter`. - ##### `beginning_of_week`, `end_of_week` The methods `beginning_of_week` and `end_of_week` return the dates for the @@ -3152,6 +3123,8 @@ Date.new(2012, 2, 29).years_ago(3) # => Sat, 28 Feb 2009 Date.new(2012, 2, 29).years_since(3) # => Sat, 28 Feb 2015 ``` +`last_year` is short-hand for `#years_ago(1)`. + ##### `months_ago`, `months_since` The methods `months_ago` and `months_since` work analogously for months: @@ -3168,6 +3141,8 @@ Date.new(2010, 4, 30).months_ago(2) # => Sun, 28 Feb 2010 Date.new(2009, 12, 31).months_since(2) # => Sun, 28 Feb 2010 ``` +`last_month` is short-hand for `#months_ago(1)`. + ##### `weeks_ago` The method `weeks_ago` works analogously for weeks: @@ -3330,35 +3305,7 @@ WARNING: `DateTime` is not aware of DST rules and so some of these methods have NOTE: All the following methods are defined in `active_support/core_ext/date_time/calculations.rb`. -The class `DateTime` is a subclass of `Date` so by loading `active_support/core_ext/date/calculations.rb` you inherit these methods and their aliases, except that they will always return datetimes: - -```ruby -yesterday -tomorrow -beginning_of_week (at_beginning_of_week) -end_of_week (at_end_of_week) -monday -sunday -weeks_ago -prev_week (last_week) -next_week -months_ago -months_since -beginning_of_month (at_beginning_of_month) -end_of_month (at_end_of_month) -prev_month (last_month) -next_month -beginning_of_quarter (at_beginning_of_quarter) -end_of_quarter (at_end_of_quarter) -beginning_of_year (at_beginning_of_year) -end_of_year (at_end_of_year) -years_ago -years_since -prev_year (last_year) -next_year -on_weekday? -on_weekend? -``` +The class `DateTime` is a subclass of `Date` so by loading `active_support/core_ext/date/calculations.rb` you inherit these methods and their aliases, except that they will always return datetimes. The following methods are reimplemented so you do **not** need to load `active_support/core_ext/date/calculations.rb` for these ones: @@ -3506,8 +3453,6 @@ Extensions to `Time` NOTE: All the following methods are defined in `active_support/core_ext/time/calculations.rb`. -Active Support adds to `Time` many of the methods available for `DateTime`: - ```ruby past? today? @@ -3519,6 +3464,8 @@ change advance ago since (in) +prev_day +next_day beginning_of_day (midnight, at_midnight, at_beginning_of_day) end_of_day beginning_of_hour (at_beginning_of_hour) @@ -3534,15 +3481,17 @@ months_ago months_since beginning_of_month (at_beginning_of_month) end_of_month (at_end_of_month) -prev_month (last_month) +prev_month next_month +last_month beginning_of_quarter (at_beginning_of_quarter) end_of_quarter (at_end_of_quarter) beginning_of_year (at_beginning_of_year) end_of_year (at_end_of_year) years_ago years_since -prev_year (last_year) +prev_year +last_year next_year on_weekday? on_weekend? @@ -3600,6 +3549,74 @@ now.all_year # => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00 ``` +#### `prev_day`, `next_day` + +In Ruby 1.9 `prev_day` and `next_day` return the date in the last or next day: + +```ruby +d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 +d.prev_day # => Fri, 07 May 2010 +d.next_day # => Sun, 09 May 2010 +``` + +#### `prev_month`, `next_month` + +In Ruby 1.9 `prev_month` and `next_month` return the date with the same day in the last or next month: + +```ruby +d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 +d.prev_month # => Thu, 08 Apr 2010 +d.next_month # => Tue, 08 Jun 2010 +``` + +If such a day does not exist, the last day of the corresponding month is returned: + +```ruby +Date.new(2000, 5, 31).prev_month # => Sun, 30 Apr 2000 +Date.new(2000, 3, 31).prev_month # => Tue, 29 Feb 2000 +Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000 +Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000 +``` + +#### `prev_year`, `next_year` + +In Ruby 1.9 `prev_year` and `next_year` return a date with the same day/month in the last or next year: + +```ruby +d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 +d.prev_year # => Fri, 08 May 2009 +d.next_year # => Sun, 08 May 2011 +``` + +If date is the 29th of February of a leap year, you obtain the 28th: + +```ruby +d = Date.new(2000, 2, 29) # => Tue, 29 Feb 2000 +d.prev_year # => Sun, 28 Feb 1999 +d.next_year # => Wed, 28 Feb 2001 +``` + +#### `prev_quarter`, `next_quarter` + +`prev_quarter` and `next_quarter` return the date with the same day in the previous or next quarter: + +```ruby +t = Time.local(2010, 5, 8) # => 2010-05-08 00:00:00 +0300 +t.prev_quarter # => 2010-02-08 00:00:00 +0200 +t.next_quarter # => 2010-08-08 00:00:00 +0300 +``` + +If such a day does not exist, the last day of the corresponding month is returned: + +```ruby +Time.local(2000, 7, 31).prev_quarter # => 2000-04-30 00:00:00 +0300 +Time.local(2000, 5, 31).prev_quarter # => 2000-02-29 00:00:00 +0200 +Time.local(2000, 10, 31).prev_quarter # => 2000-07-31 00:00:00 +0300 +Time.local(2000, 11, 31).next_quarter # => 2001-03-01 00:00:00 +0200 +``` + +`prev_quarter` is aliased to `last_quarter`. + ### Time Constructors Active Support defines `Time.current` to be `Time.zone.now` if there's a user time zone defined, with fallback to `Time.now`: @@ -3623,7 +3640,7 @@ Durations can be added to and subtracted from time objects: now = Time.current # => Mon, 09 Aug 2010 23:20:05 UTC +00:00 now + 1.year -# => Tue, 09 Aug 2011 23:21:11 UTC +00:00 +# => Tue, 09 Aug 2011 23:21:11 UTC +00:00 now - 1.week # => Mon, 02 Aug 2010 23:21:11 UTC +00:00 ``` @@ -3686,9 +3703,9 @@ Extensions to `NameError` Active Support adds `missing_name?` to `NameError`, which tests whether the exception was raised because of the name passed as argument. -The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name. +The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully qualified constant name. -TIP: A symbol can represent a fully-qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically. +TIP: A symbol can represent a fully qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically. For example, when an action of `ArticlesController` is called Rails tries optimistically to use `ArticlesHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `articles_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases: diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 03c9183eb3..11c4a8222a 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -197,6 +197,12 @@ INFO. Additional keys may be added by the caller. } ``` +### unpermitted_parameters.action_controller + +| Key | Value | +| ------- | ---------------- | +| `:keys` | Unpermitted keys | + Action View ----------- @@ -304,7 +310,7 @@ Action Mailer mailer: "Notification", message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail", subject: "Rails Guides", - to: ["users@rails.com", "ddh@rails.com"], + to: ["users@rails.com", "dhh@rails.com"], from: ["me@rails.com"], date: Sat, 10 Mar 2012 14:18:09 +0100, mail: "..." # omitted for brevity @@ -330,13 +336,29 @@ Action Mailer mailer: "Notification", message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail", subject: "Rails Guides", - to: ["users@rails.com", "ddh@rails.com"], + to: ["users@rails.com", "dhh@rails.com"], from: ["me@rails.com"], date: Sat, 10 Mar 2012 14:18:09 +0100, mail: "..." # omitted for brevity } ``` +### process.action_mailer + +| Key | Value | +| ------------- | ------------------------ | +| `:mailer` | Name of the mailer class | +| `:action` | The action | +| `:args` | The arguments | + +```ruby +{ + mailer: "Notification", + action: "welcome_email", + args: [] +} +``` + Active Support -------------- @@ -450,6 +472,99 @@ Active Job | `:adapter` | QueueAdapter object processing the job | | `:job` | Job object | +Action Cable +------------ + +### perform_action.action_cable + +| Key | Value | +| ---------------- | ------------------------- | +| `:channel_class` | Name of the channel class | +| `:action` | The action | +| `:data` | A hash of data | + +### transmit.action_cable + +| Key | Value | +| ---------------- | ------------------------- | +| `:channel_class` | Name of the channel class | +| `:data` | A hash of data | +| `:via` | Via | + +### transmit_subscription_confirmation.action_cable + +| Key | Value | +| ---------------- | ------------------------- | +| `:channel_class` | Name of the channel class | + +### transmit_subscription_rejection.action_cable + +| Key | Value | +| ---------------- | ------------------------- | +| `:channel_class` | Name of the channel class | + +### broadcast.action_cable + +| Key | Value | +| --------------- | -------------------- | +| `:broadcasting` | A named broadcasting | +| `:message` | A hash of message | +| `:coder` | The coder | + +Active Storage +-------------- + +### service_upload.active_storage + +| Key | Value | +| ------------ | ---------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:checksum` | Checksum to ensure integrity | + +### service_streaming_download.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_download.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_delete.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_delete_prefixed.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:prefix` | Key prefix | +| `:service` | Name of the service | + +### service_exist.active_storage + +| Key | Value | +| ------------ | --------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:exist` | File or blob exists or not | + +### service_url.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:url` | Generated url | Railties -------- @@ -549,4 +664,4 @@ end ``` You should follow Rails conventions when defining your own events. The format is: `event.library`. -If you application is sending Tweets, you should create an event named `tweet.twitter`. +If your application is sending Tweets, you should create an event named `tweet.twitter`. diff --git a/guides/source/api_app.md b/guides/source/api_app.md index f373d313cc..b4d90d31de 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -1,6 +1,5 @@ **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** - Using Rails for API-only Applications ===================================== @@ -18,7 +17,7 @@ What is an API Application? Traditionally, when people said that they used Rails as an "API", they meant providing a programmatically accessible API alongside their web application. -For example, GitHub provides [an API](http://developer.github.com) that you +For example, GitHub provides [an API](https://developer.github.com) that you can use from your own custom clients. With the advent of client-side frameworks, more developers are using Rails to @@ -66,9 +65,9 @@ Handled at the middleware layer: about the request environment, database queries, and basic performance information. - Security: Rails detects and thwarts [IP spoofing - attacks](http://en.wikipedia.org/wiki/IP_address_spoofing) and handles + attacks](https://en.wikipedia.org/wiki/IP_address_spoofing) and handles cryptographic signatures in a [timing - attack](http://en.wikipedia.org/wiki/Timing_attack) aware way. Don't know what + attack](https://en.wikipedia.org/wiki/Timing_attack) aware way. Don't know what an IP spoofing attack or a timing attack is? Exactly. - Parameter Parsing: Want to specify your parameters as JSON instead of as a URL-encoded String? No problem. Rails will decode the JSON for you and make @@ -94,7 +93,7 @@ Handled at the Action Pack layer: means not having to spend time thinking about how to model your API in terms of HTTP. - URL Generation: The flip side of routing is URL generation. A good API based - on HTTP includes URLs (see [the GitHub Gist API](http://developer.github.com/v3/gists/) + on HTTP includes URLs (see [the GitHub Gist API](https://developer.github.com/v3/gists/) for an example). - Header and Redirection Responses: `head :no_content` and `redirect_to user_url(current_user)` come in handy. Sure, you could manually @@ -206,10 +205,10 @@ An API application comes with the following middleware by default: - `ActiveSupport::Cache::Strategy::LocalCache::Middleware` - `Rack::Runtime` - `ActionDispatch::RequestId` +- `ActionDispatch::RemoteIp` - `Rails::Rack::Logger` - `ActionDispatch::ShowExceptions` - `ActionDispatch::DebugExceptions` -- `ActionDispatch::RemoteIp` - `ActionDispatch::Reloader` - `ActionDispatch::Callbacks` - `ActiveRecord::Migration::CheckPending` @@ -360,7 +359,7 @@ middleware set, you can remove it with: config.middleware.delete ::Rack::Sendfile ``` -Keep in mind that removing these middleware will remove support for certain +Keep in mind that removing these middlewares will remove support for certain features in Action Controller. Choosing Controller Modules @@ -385,8 +384,9 @@ controller modules by default: hooks defined by Action Controller (see [the instrumentation guide](active_support_instrumentation.html#action-controller) for more information regarding this). -- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash, +- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash, so that you don't have to specify root elements sending POST requests for instance. +- `ActionController::Head`: Support for returning a response with no content, only headers Other plugins may add additional modules. You can get a list of all modules included into `ActionController::API` in the rails console: @@ -394,12 +394,12 @@ included into `ActionController::API` in the rails console: ```bash $ bin/rails c >> ActionController::API.ancestors - ActionController::Metal.ancestors -=> [ActionController::API, - ActiveRecord::Railties::ControllerRuntime, - ActionDispatch::Routing::RouteSet::MountedHelpers, - ActionController::ParamsWrapper, - ... , - AbstractController::Rendering, +=> [ActionController::API, + ActiveRecord::Railties::ControllerRuntime, + ActionDispatch::Routing::RouteSet::MountedHelpers, + ActionController::ParamsWrapper, + ... , + AbstractController::Rendering, ActionView::ViewPaths] ``` @@ -413,8 +413,10 @@ Some common modules you might want to add: - `AbstractController::Translation`: Support for the `l` and `t` localization and translation methods. -- `ActionController::HttpAuthentication::Basic` (or `Digest` or `Token`): Support - for basic, digest or token HTTP authentication. +- Support for basic, digest or token HTTP authentication: + * `ActionController::HttpAuthentication::Basic::ControllerMethods`, + * `ActionController::HttpAuthentication::Digest::ControllerMethods`, + * `ActionController::HttpAuthentication::Token::ControllerMethods` - `ActionView::Layouts`: Support for layouts when rendering. - `ActionController::MimeResponds`: Support for `respond_to`. - `ActionController::Cookies`: Support for `cookies`, which includes diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 34b9c0d2ca..10b89433e7 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -16,7 +16,7 @@ RDoc ---- The [Rails API documentation](http://api.rubyonrails.org) is generated with -[RDoc](http://docs.seattlerb.org/rdoc/). To generate it, make sure you are +[RDoc](https://ruby.github.io/rdoc/). To generate it, make sure you are in the rails root directory, run `bundle install` and execute: ```bash @@ -26,9 +26,9 @@ in the rails root directory, run `bundle install` and execute: Resulting HTML files can be found in the ./doc/rdoc directory. Please consult the RDoc documentation for help with the -[markup](http://docs.seattlerb.org/rdoc/RDoc/Markup.html), +[markup](https://ruby.github.io/rdoc/RDoc/Markup.html), and also take into account these [additional -directives](http://docs.seattlerb.org/rdoc/RDoc/Parser/Ruby.html). +directives](https://ruby.github.io/rdoc/RDoc/Parser/Ruby.html). Wording ------- @@ -82,12 +82,12 @@ used. Instead of: English ------- -Please use American English (*color*, *center*, *modularize*, etc). See [a list of American and British English spelling differences here](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences). +Please use American English (*color*, *center*, *modularize*, etc). See [a list of American and British English spelling differences here](https://en.wikipedia.org/wiki/American_and_British_English_spelling_differences). Oxford Comma ------------ -Please use the [Oxford comma](http://en.wikipedia.org/wiki/Serial_comma) +Please use the [Oxford comma](https://en.wikipedia.org/wiki/Serial_comma) ("red, white, and blue", instead of "red, white and blue"). Example Code @@ -281,7 +281,7 @@ Methods created with `(module|class)_eval(STRING)` have a comment by their side ```ruby for severity in Severity.constants - class_eval <<-EOT, __FILE__, __LINE__ + class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block) add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block) end # end @@ -333,10 +333,6 @@ As a contributor, it's important to think about whether this API is meant for en A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly. -If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project. - -A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails. - To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first. Regarding the Rails Stack @@ -354,7 +350,7 @@ into account, one such example is ```ruby # image_tag("icon.png") -# # => <img alt="Icon" src="/assets/icon.png" /> +# # => <img src="/assets/icon.png" /> ``` Although the default behavior for `#image_tag` is to always return diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 360de9a584..e6d5aed135 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -35,7 +35,7 @@ rails new appname --skip-sprockets ``` Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier` -gems to your Gemfile, which are used by Sprockets for asset compression: +gems to your `Gemfile`, which are used by Sprockets for asset compression: ```ruby gem 'sass-rails' @@ -44,8 +44,8 @@ gem 'coffee-rails' ``` Using the `--skip-sprockets` option will prevent Rails from adding -them to your Gemfile, so if you later want to enable -the asset pipeline you will have to add those gems to your Gemfile. Also, +them to your `Gemfile`, so if you later want to enable +the asset pipeline you will have to add those gems to your `Gemfile`. Also, creating an application with the `--skip-sprockets` option will generate a slightly different `config/application.rb` file, with a require statement for the sprockets railtie that is commented-out. You will have to remove @@ -65,7 +65,7 @@ config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included -in the Gemfile and no `config.assets.css_compressor` option is set. +in the `Gemfile` and no `config.assets.css_compressor` option is set. ### Main Features @@ -154,7 +154,7 @@ environments. You can enable or disable it in your configuration through the More reading: -* [Optimize caching](http://code.google.com/speed/page-speed/docs/caching.html) +* [Optimize caching](https://developers.google.com/speed/docs/insights/LeverageBrowserCaching) * [Revving Filenames: don't use querystring](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/) @@ -181,7 +181,7 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates -the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the +the file `scaffolds.css` (or `scaffolds.scss` if `sass-rails` is in the `Gemfile`.) For example, if you generate a `ProjectsController`, Rails will also add a new @@ -202,12 +202,12 @@ will result in your assets being included more than once. WARNING: When using asset precompilation, you will need to ensure that your controller assets will be precompiled when loading them on a per page basis. By -default .coffee and .scss files will not be precompiled on their own. See +default `.coffee` and `.scss` files will not be precompiled on their own. See [Precompiling Assets](#precompiling-assets) for more information on how precompiling works. NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript. -If you are using Mac OS X or Windows, you have a JavaScript runtime installed in +If you are using macOS or Windows, you have a JavaScript runtime installed in your operating system. Check [ExecJS](https://github.com/rails/execjs#readme) documentation to know all supported JavaScript runtimes. You can also disable generation of controller specific asset files by adding the @@ -283,10 +283,10 @@ You can view the search path by inspecting `Rails.application.config.assets.paths` in the Rails console. Besides the standard `assets/*` paths, additional (fully qualified) paths can be -added to the pipeline in `config/application.rb`. For example: +added to the pipeline in `config/initializers/assets.rb`. For example: ```ruby -config.assets.paths << Rails.root.join("lib", "videoplayer", "flash") +Rails.application.config.assets.paths << Rails.root.join("lib", "videoplayer", "flash") ``` Paths are traversed in the order they occur in the search path. By default, @@ -335,7 +335,7 @@ an asset has been updated and if so loads it into the page: <%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %> ``` -In regular views you can access images in the `public/assets/images` directory +In regular views you can access images in the `app/assets/images` directory like this: ```erb @@ -383,7 +383,7 @@ it would make sense to have an image in one of the asset load paths, such as already available in `public/assets` as a fingerprinted file, then that path is referenced. -If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) - +If you want to use a [data URI](https://en.wikipedia.org/wiki/Data_URI_scheme) - a method of embedding the image data directly into the CSS file - you can use the `asset_data_uri` helper. @@ -447,15 +447,15 @@ For example, a new Rails application includes a default ```js // ... -//= require jquery -//= require jquery_ujs +//= require rails-ujs +//= require turbolinks //= require_tree . ``` In JavaScript files, Sprockets directives begin with `//=`. In the above case, the file is using the `require` and the `require_tree` directives. The `require` directive is used to tell Sprockets the files you wish to require. Here, you are -requiring the files `jquery.js` and `jquery_ujs.js` that are available somewhere +requiring the files `rails-ujs.js` and `turbolinks.js` that are available somewhere in the search path for Sprockets. You need not supply the extensions explicitly. Sprockets assumes you are requiring a `.js` file when done from within a `.js` file. @@ -572,20 +572,6 @@ would generate this HTML: The `body` param is required by Sprockets. -### Runtime Error Checking - -By default the asset pipeline will check for potential errors in development mode during -runtime. To disable this behavior you can set: - -```ruby -config.assets.raise_runtime_errors = false -``` - -When this option is true, the asset pipeline will check if all the assets loaded -in your application are included in the `config.assets.precompile` list. -If `config.assets.digest` is also true, the asset pipeline will require that -all requests for assets include digests. - ### Raise an Error When an Asset is Not Found If you are using sprockets-rails >= 3.2.0 you can configure what happens @@ -740,7 +726,7 @@ include, you can add them to the `precompile` array in `config/initializers/asse Rails.application.config.assets.precompile += %w( admin.js admin.css ) ``` -NOTE. Always specify an expected compiled filename that ends with .js or .css, +NOTE. Always specify an expected compiled filename that ends with `.js` or `.css`, even if you want to add Sass or CoffeeScript files to the precompile array. The task also generates a `.sprockets-manifest-md5hash.json` (where `md5hash` is @@ -864,18 +850,18 @@ This mode uses more memory, performs more poorly than the default and is not recommended. If you are deploying a production application to a system without any -pre-existing JavaScript runtimes, you may want to add one to your Gemfile: +pre-existing JavaScript runtimes, you may want to add one to your `Gemfile`: ```ruby group :production do - gem 'therubyracer' + gem 'mini_racer' end ``` ### CDNs CDN stands for [Content Delivery -Network](http://en.wikipedia.org/wiki/Content_delivery_network), they are +Network](https://en.wikipedia.org/wiki/Content_delivery_network), they are primarily designed to cache assets all over the world so that when a browser requests the asset, a cached copy will be geographically close to that browser. If you are serving assets directly from your Rails server in production, the @@ -923,7 +909,7 @@ domain, you do not need to specify a protocol or "scheme" such as `http://` or that is generated will match how the webpage is accessed by default. You can also set this value through an [environment -variable](http://en.wikipedia.org/wiki/Environment_variable) to make running a +variable](https://en.wikipedia.org/wiki/Environment_variable) to make running a staging copy of your site easier: ``` @@ -1083,7 +1069,7 @@ Customizing the Pipeline ### CSS Compression One of the options for compressing CSS is YUI. The [YUI CSS -compressor](http://yui.github.io/yuicompressor/css.html) provides +compressor](https://yui.github.io/yuicompressor/css.html) provides minification. The following line enables YUI compression, and requires the `yui-compressor` @@ -1104,7 +1090,7 @@ Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. -The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). +The default `Gemfile` includes [uglifier](https://github.com/lautis/uglifier). This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space and comments, shortening local variable names, and performing other micro-optimizations such @@ -1117,7 +1103,7 @@ config.assets.js_compressor = :uglifier ``` NOTE: You will need an [ExecJS](https://github.com/rails/execjs#readme) -supported runtime in order to use `uglifier`. If you are using Mac OS X or +supported runtime in order to use `uglifier`. If you are using macOS or Windows you have a JavaScript runtime installed in your operating system. @@ -1233,7 +1219,7 @@ Sprockets uses Processors, Transformers, Compressors, and Exporters to extend Sprockets functionality. Have a look at [Extending Sprockets](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md) to learn more. Here we registered a preprocessor to add a comment to the end -of text/css (.css) files. +of text/css (`.css`) files. ```ruby module AddComment diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 6e68935f9b..b5e236b790 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -154,7 +154,7 @@ case, the column definition might look like this: ```ruby create_table :accounts do |t| - t.belongs_to :supplier, index: true, unique: true, foreign_key: true + t.belongs_to :supplier, index: { unique: true }, foreign_key: true # ... end ``` @@ -582,14 +582,30 @@ class CreateBooks < ActiveRecord::Migration[5.0] t.string :book_number t.integer :author_id end - - add_index :books, :author_id end end ``` If you create an association some time after you build the underlying model, you need to remember to create an `add_column` migration to provide the necessary foreign key. +It's a good practice to add an index on the foreign key to improve queries +performance and a foreign key constraint to ensure referential data integrity: + +```ruby +class CreateBooks < ActiveRecord::Migration[5.0] + def change + create_table :books do |t| + t.datetime :published_at + t.string :book_number + t.integer :author_id + end + + add_index :books, :author_id + add_foreign_key :books, :authors + end +end +``` + #### Creating Join Tables for `has_and_belongs_to_many` Associations If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering. @@ -647,11 +663,11 @@ By default, associations look for objects only within the current module's scope module MyApplication module Business class Supplier < ApplicationRecord - has_one :account + has_one :account end class Account < ApplicationRecord - belongs_to :supplier + belongs_to :supplier end end end @@ -663,13 +679,13 @@ This will work fine, because both the `Supplier` and the `Account` class are def module MyApplication module Business class Supplier < ApplicationRecord - has_one :account + has_one :account end end module Billing class Account < ApplicationRecord - belongs_to :supplier + belongs_to :supplier end end end @@ -681,14 +697,14 @@ To associate a model with a model in a different namespace, you must specify the module MyApplication module Business class Supplier < ApplicationRecord - has_one :account, + has_one :account, class_name: "MyApplication::Billing::Account" end end module Billing class Account < ApplicationRecord - belongs_to :supplier, + belongs_to :supplier, class_name: "MyApplication::Business::Supplier" end end @@ -795,6 +811,7 @@ When you declare a `belongs_to` association, the declaring class automatically g * `build_association(attributes = {})` * `create_association(attributes = {})` * `create_association!(attributes = {})` +* `reload_association` In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: @@ -812,6 +829,7 @@ author= build_author create_author create_author! +reload_author ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -824,10 +842,10 @@ The `association` method returns the associated object, if any. If no associated @author = @book.author ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload_association` on the parent object. ```ruby -@author = @book.reload.author +@author = @book.reload_author ``` ##### `association=(associate)` @@ -888,7 +906,7 @@ The `belongs_to` association supports these options: ##### `:autosave` -If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object. +If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved. ##### `:class_name` @@ -944,19 +962,18 @@ class Author < ApplicationRecord end ``` -NOTE: You only need to specify the :counter_cache option on the `belongs_to` +NOTE: You only need to specify the `:counter_cache` option on the `belongs_to` side of the association. Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`. ##### `:dependent` -Controls what happens to associated objects when their owner is destroyed: +If you set the `:dependent` option to: -* `:destroy` causes the associated objects to also be destroyed. -* `:delete_all` causes the associated objects to be deleted directly from the database (callbacks are not executed). -* `: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 associated records. -* `:restrict_with_error` causes an error to be added to the owner if there are associated objects. +* `:destroy`, when the object is destroyed, `destroy` will be called on its +associated objects. +* `:delete`, when the object is destroyed, all its associated objects will be +deleted directly from the database without calling their `destroy` method. WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database. @@ -1145,6 +1162,7 @@ When you declare a `has_one` association, the declaring class automatically gain * `build_association(attributes = {})` * `create_association(attributes = {})` * `create_association!(attributes = {})` +* `reload_association` In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: @@ -1162,6 +1180,7 @@ account= build_account create_account create_account! +reload_account ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -1174,10 +1193,10 @@ The `association` method returns the associated object, if any. If no associated @account = @supplier.account ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload_association` on the parent object. ```ruby -@account = @supplier.reload.account +@account = @supplier.reload_account ``` ##### `association=(associate)` @@ -1238,7 +1257,7 @@ Setting the `:as` option indicates that this is a polymorphic association. Polym ##### `:autosave` -If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object. +If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved. ##### `:class_name` @@ -1401,7 +1420,7 @@ If either of these saves fails due to validation errors, then the assignment sta If the parent object (the one declaring the `has_one` association) is unsaved (that is, `new_record?` returns `true`) then the child objects are not saved. They will automatically when the parent object is saved. -If you want to assign an object to a `has_one` association without saving the object, use the `association.build` method. +If you want to assign an object to a `has_one` association without saving the object, use the `build_association` method. ### `has_many` Association Reference @@ -1427,6 +1446,7 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection.build(attributes = {}, ...)` * `collection.create(attributes = {})` * `collection.create!(attributes = {})` +* `collection.reload` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1455,11 +1475,12 @@ books.exists?(...) books.build(attributes = {}, ...) books.create(attributes = {}) books.create!(attributes = {}) +books.reload ``` ##### `collection` -The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. +The `collection` method returns a Relation of all of the associated objects. If there are no associated objects, it returns an empty Relation. ```ruby @books = @author.books @@ -1540,10 +1561,11 @@ The `collection.size` method returns the number of objects in the collection. ##### `collection.find(...)` -The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`. +The `collection.find` method finds objects within the collection. It uses the same syntax and options as +[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). ```ruby -@available_books = @author.books.find(1) +@available_book = @author.books.find(1) ``` ##### `collection.where(...)` @@ -1593,6 +1615,14 @@ The `collection.create` method returns a single or array of new objects of the a Does the same as `collection.create` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. +##### `collection.reload` + +The `collection.reload` method returns a Relation of all of the associated objects, forcing a database read. If there are no associated objects, it returns an empty Relation. + +```ruby +@books = @author.books.reload +``` + #### Options for `has_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1624,7 +1654,7 @@ Setting the `:as` option indicates that this is a polymorphic association, as di ##### `:autosave` -If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object. +If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved. ##### `:class_name` @@ -1815,7 +1845,7 @@ The `limit` method lets you restrict the total number of objects that will be fe class Author < ApplicationRecord has_many :recent_books, -> { order('published_at desc').limit(100) }, - class_name: "Book", + class_name: "Book" end ``` @@ -1949,6 +1979,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection.build(attributes = {})` * `collection.create(attributes = {})` * `collection.create!(attributes = {})` +* `collection.reload` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1977,6 +2008,7 @@ assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) assemblies.create!(attributes = {}) +assemblies.reload ``` ##### Additional Column Methods @@ -1988,7 +2020,7 @@ WARNING: The use of extra attributes on the join table in a `has_and_belongs_to_ ##### `collection` -The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. +The `collection` method returns a Relation of all of the associated objects. If there are no associated objects, it returns an empty Relation. ```ruby @assemblies = @part.assemblies @@ -2060,7 +2092,8 @@ The `collection.size` method returns the number of objects in the collection. ##### `collection.find(...)` -The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`. It also adds the additional condition that the object must be in the collection. +The `collection.find` method finds objects within the collection. It uses the same syntax and options as +[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). ```ruby @assembly = @part.assemblies.find(1) @@ -2068,7 +2101,7 @@ The `collection.find` method finds objects within the collection. It uses the sa ##### `collection.where(...)` -The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. It also adds the additional condition that the object must be in the collection. +The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. ```ruby @new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago) @@ -2100,6 +2133,14 @@ The `collection.create` method returns a new object of the associated type. This Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` if the record is invalid. +##### `collection.reload` + +The `collection.reload` method returns a Relation of all of the associated objects, forcing a database read. If there are no associated objects, it returns an empty Relation. + +```ruby +@assemblies = @part.assemblies.reload +``` + #### Options for `has_and_belongs_to_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -2137,7 +2178,7 @@ end ##### `:autosave` -If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object. +If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved. ##### `:class_name` diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index 61657023e7..dea87a18f8 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -330,11 +330,17 @@ its resolution next. Let's define *parent* to be that qualifying class or module object, that is, `Billing` in the example above. The algorithm for qualified constants goes like this: -1. The constant is looked up in the parent and its ancestors. +1. The constant is looked up in the parent and its ancestors. In Ruby >= 2.5, +`Object` is skipped if present among the ancestors. `Kernel` and `BasicObject` +are still checked though. 2. If the lookup fails, `const_missing` is invoked in the parent. The default implementation of `const_missing` raises `NameError`, but it can be overridden. +INFO. In Ruby < 2.5 `String::Hash` evaluates to `Hash` and the interpreter +issues a warning: "toplevel constant Hash referenced by String::Hash". Starting +with 2.5, `String::Hash` raises `NameError` because `Object` is skipped. + As you see, this algorithm is simpler than the one for relative constants. In particular, the nesting plays no role here, and modules are not special-cased, if neither they nor their ancestors have the constants, `Object` is **not** @@ -475,12 +481,21 @@ it is (edited): ``` $ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' .../app/assets +.../app/channels .../app/controllers +.../app/controllers/concerns .../app/helpers +.../app/jobs .../app/mailers .../app/models -.../app/controllers/concerns .../app/models/concerns +.../activestorage/app/assets +.../activestorage/app/controllers +.../activestorage/app/javascript +.../activestorage/app/jobs +.../activestorage/app/models +.../actioncable/app/assets +.../actionview/app/assets .../test/mailers/previews ``` @@ -945,7 +960,7 @@ to work on some subclass, things get interesting. While working with `Polygon` you do not need to be aware of all its descendants, because anything in the table is by definition a polygon, but when working with subclasses Active Record needs to be able to enumerate the types it is looking -for. Let’s see an example. +for. Let's see an example. `Rectangle.all` only loads rectangles by adding a type constraint to the query: @@ -954,7 +969,7 @@ SELECT "polygons".* FROM "polygons" WHERE "polygons"."type" IN ("Rectangle") ``` -Let’s introduce now a subclass of `Rectangle`: +Let's introduce now a subclass of `Rectangle`: ```ruby # app/models/square.rb @@ -969,7 +984,7 @@ SELECT "polygons".* FROM "polygons" WHERE "polygons"."type" IN ("Rectangle", "Square") ``` -But there’s a caveat here: How does Active Record know that the class `Square` +But there's a caveat here: How does Active Record know that the class `Square` exists at all? Even if the file `app/models/square.rb` exists and defines the `Square` class, @@ -983,20 +998,19 @@ WHERE "polygons"."type" IN ("Rectangle") That is not a bug, the query includes all *known* descendants of `Rectangle`. A way to ensure this works correctly regardless of the order of execution is to -load the leaves of the tree by hand at the bottom of the file that defines the -root class: +manually load the direct subclasses at the bottom of the file that defines each +intermediate class: ```ruby -# app/models/polygon.rb -class Polygon < ApplicationRecord +# app/models/rectangle.rb +class Rectangle < Polygon end -require_dependency ‘square’ +require_dependency 'square' ``` -Only the leaves that are **at least grandchildren** need to be loaded this -way. Direct subclasses do not need to be preloaded. If the hierarchy is -deeper, intermediate classes will be autoloaded recursively from the bottom -because their constant will appear in the class definitions as superclass. +This needs to happen for every intermediate (non-root and non-leaf) class. The +root class does not scope the query by type, and therefore does not necessarily +have to know all its descendants. ### Autoloading and `require` @@ -1041,7 +1055,7 @@ end The purpose of this setup would be that the application uses the class that corresponds to the environment via `AUTH_SERVICE`. In development mode -`MockedAuthService` gets autoloaded when the initializer runs. Let’s suppose +`MockedAuthService` gets autoloaded when the initializer runs. Let's suppose we do some requests, change its implementation, and hit the application again. To our surprise the changes are not reflected. Why? @@ -1170,6 +1184,8 @@ end #### Qualified References +WARNING. This gotcha is only possible in Ruby < 2.5. + Given ```ruby diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index fd7626250c..780e69c146 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -32,7 +32,7 @@ Basic Caching This is an introduction to three types of caching techniques: page, action and fragment caching. By default Rails provides fragment caching. In order to use page and action caching you will need to add `actionpack-page_caching` and -`actionpack-action_caching` to your Gemfile. +`actionpack-action_caching` to your `Gemfile`. By default, caching is only enabled in your production environment. To play around with caching locally you'll want to enable caching in your local @@ -175,10 +175,28 @@ class Game < ApplicationRecord end ``` -With `touch` set to true, any action which changes `updated_at` for a game +With `touch` set to `true`, any action which changes `updated_at` for a game record will also change it for the associated product, thereby expiring the cache. +### Shared Partial Caching + +It is possible to share partials and associated caching between files with different mime types. For example shared partial caching allows template writers to share a partial between HTML and JavaScript files. When templates are collected in the template resolver file paths they only include the template language extension and not the mime type. Because of this templates can be used for multiple mime types. Both HTML and JavaScript requests will respond to the following code: + +```ruby +render(partial: 'hotels/hotel', collection: @hotels, cached: true) +``` + +Will load a file named `hotels/hotel.erb`. + +Another option is to include the full filename of the partial to render. + +```ruby +render(partial: 'hotels/hotel.html.erb', collection: @hotels, cached: true) +``` + +Will load a file named `hotels/hotel.html.erb` in any file mime type, for example you could include this partial in a JavaScript file. + ### Managing dependencies In order to correctly invalidate the cache, you need to properly define the @@ -272,7 +290,7 @@ Sometimes you need to cache a particular value or query result instead of cachin The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, that block will be executed in the event of a cache miss. The return value of the block will be written to the cache under the given cache key, and that return value will be returned. In case of cache hit, the cached value will be returned without executing the block. -Consider the following example. An application has a `Product` model with an instance method that looks up the product’s price on a competing website. The data returned by this method would be perfect for low-level caching: +Consider the following example. An application has a `Product` model with an instance method that looks up the product's price on a competing website. The data returned by this method would be perfect for low-level caching: ```ruby class Product < ApplicationRecord @@ -284,7 +302,7 @@ class Product < ApplicationRecord 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` 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. ### SQL Caching @@ -348,9 +366,9 @@ There are some common options used by all cache implementations. These can be pa * `:namespace` - This option can be used to create a namespace within the cache store. It is especially useful if your application shares a cache with other applications. -* `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network. +* `:compress` - Enabled by default. Compresses cache entries so more data can be stored in the same memory footprint, leading to fewer cache evictions and higher hit rates. -* `:compress_threshold` - This option is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes. +* `:compress_threshold` - Defaults to 1kB. Cache entries larger than this threshold, specified in bytes, are compressed. * `:expires_in` - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache. @@ -387,6 +405,11 @@ store is not appropriate for large application deployments. However, it can work well for small, low traffic sites with only a couple of server processes, as well as development and test environments. +New Rails projects are configured to use this implementation in development environment by default. + +NOTE: Since processes will not share cache data when using `:memory_store`, +it will not be possible to manually read, write or expire the cache via the Rails console. + ### ActiveSupport::Cache::FileStore This cache store uses the file system to store entries. The path to the directory where the store files will be stored must be specified when initializing the cache. @@ -396,14 +419,15 @@ config.cache_store = :file_store, "/path/to/cache/directory" ``` With this cache store, multiple server processes on the same host can share a -cache. The cache store is appropriate for low to medium traffic sites that are +cache. This cache store is appropriate for low to medium traffic sites that are served off one or two hosts. Server processes running on different hosts could share a cache by using a shared file system, but that setup is not recommended. As the cache will grow until the disk is full, it is recommended to periodically clear out old entries. -This is the default cache store implementation. +This is the default cache store implementation (at `"#{root}/tmp/cache/"`) if +no explicit `config.cache_store` is supplied. ### ActiveSupport::Cache::MemCacheStore @@ -420,6 +444,53 @@ The `write` and `fetch` methods on this cache accept two additional options that config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com" ``` +### ActiveSupport::Cache::RedisCacheStore + +The Redis cache store takes advantage of Redis support for least-recently-used +and least-frequently-used key eviction when it reaches max memory, allowing it +to behave much like a Memcached cache server. + +Deployment note: Redis doesn't expire keys by default, so take care to use a +dedicated Redis cache server. Don't fill up your persistent-Redis server with +volatile cache data! Read the +[Redis cache server setup guide](https://redis.io/topics/lru-cache) in detail. + +For an all-cache Redis server, set `maxmemory-policy` to an `allkeys` policy. +Redis 4+ support least-frequently-used (`allkeys-lfu`) eviction, an excellent +default choice. Redis 3 and earlier should use `allkeys-lru` for +least-recently-used eviction. + +Set cache read and write timeouts relatively low. Regenerating a cached value +is often faster than waiting more than a second to retrieve it. Both read and +write timeouts default to 1 second, but may be set lower if your network is +consistently low latency. + +Cache reads and writes never raise exceptions. They just return `nil` instead, +behaving as if there was nothing in the cache. To gauge whether your cache is +hitting exceptions, you may provide an `error_handler` to report to an +exception gathering service. It must accept three keyword arguments: `method`, +the cache store method that was originally called; `returning`, the value that +was returned to the user, typically `nil`; and `exception`, the exception that +was rescued. + +Putting it all together, a production Redis cache store may look something +like this: + +```ruby +cache_servers = %w[ "redis://cache-01:6379/0", "redis://cache-02:6379/0", … ], +config.cache_store = :redis_cache_store, url: cache_servers, + + connect_timeout: 30, # Defaults to 20 seconds + read_timeout: 0.2, # Defaults to 1 second + write_timeout: 0.2, # Defaults to 1 second + + error_handler: -> (method:, returning:, exception:) { + # Report errors to Sentry as warnings + Raven.capture_exception exception, level: 'warning", + tags: { method: method, returning: returning } + } +``` + ### ActiveSupport::Cache::NullStore This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss. @@ -570,6 +641,20 @@ You can also set the strong ETag directly on the response. response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7" ``` +Caching in Development +---------------------- + +It's common to want to test the caching strategy of your application +in development mode. Rails provides the rake task `dev:cache` to +easily toggle caching on/off. + +```bash +$ bin/rails dev:cache +Development mode is now being cached. +$ bin/rails dev:cache +Development mode is no longer being cached. +``` + References ---------- diff --git a/guides/source/command_line.md b/guides/source/command_line.md index c8d559745e..648645af7c 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -63,7 +63,7 @@ With no further work, `rails server` will run our new shiny Rails app: $ cd commandsapp $ bin/rails server => Booting Puma -=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000 +=> Rails 5.1.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.0.2 (ruby 2.3.0-p0), codename: Plethora of Penguin Pinatas @@ -102,6 +102,7 @@ Please choose a generator below. Rails: assets + channel controller generator ... @@ -241,6 +242,8 @@ $ bin/rails generate scaffold HighScore game:string score:integer invoke jbuilder create app/views/high_scores/index.json.jbuilder create app/views/high_scores/show.json.jbuilder + invoke test_unit + create test/system/high_scores_test.rb invoke assets invoke coffee create app/assets/javascripts/high_scores.coffee @@ -262,12 +265,12 @@ $ bin/rails db:migrate == CreateHighScores: migrated (0.0019s) ====================================== ``` -INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions -about code. In unit testing, we take a little part of code, say a method of a model, -and test its inputs and outputs. Unit tests are your friend. The sooner you make -peace with the fact that your quality of life will drastically increase when you unit -test your code, the better. Seriously. Please visit -[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth +INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions +about code. In unit testing, we take a little part of code, say a method of a model, +and test its inputs and outputs. Unit tests are your friend. The sooner you make +peace with the fact that your quality of life will drastically increase when you unit +test your code, the better. Seriously. Please visit +[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth look at unit testing. Let's see the interface Rails created for us. @@ -287,14 +290,14 @@ INFO: You can also use the alias "c" to invoke the console: `rails c`. You can specify the environment in which the `console` command should operate. ```bash -$ bin/rails console staging +$ bin/rails console -e staging ``` If you wish to test out some code without changing any data, you can do that by invoking `rails console --sandbox`. ```bash $ bin/rails console --sandbox -Loading development environment in sandbox (Rails 5.0.0) +Loading development environment in sandbox (Rails 5.1.0) Any modifications you make will be rolled back on exit irb(main):001:0> ``` @@ -428,12 +431,12 @@ INFO: You can also use `bin/rails -T` to get the list of tasks. ```bash $ bin/rails about About your application's environment -Rails version 5.0.0 +Rails version 5.1.0 Ruby version 2.2.2 (x86_64-linux) RubyGems version 2.4.6 -Rack version 1.6 +Rack version 2.0.1 JavaScript Runtime Node.js (V8) -Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag +Middleware: Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, ActiveSupport::Cache::Strategy::LocalCache::Middleware, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, ActionDispatch::RemoteIp, Sprockets::Rails::QuietAssets, Rails::Rack::Logger, ActionDispatch::ShowExceptions, WebConsole::Middleware, ActionDispatch::DebugExceptions, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 @@ -533,7 +536,8 @@ The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` * `rails tmp:cache:clear` clears `tmp/cache`. * `rails tmp:sockets:clear` clears `tmp/sockets`. -* `rails tmp:clear` clears all cache and sockets files. +* `rails tmp:screenshots:clear` clears `tmp/screenshots`. +* `rails tmp:clear` clears all cache, sockets and screenshot files. * `rails tmp:create` creates tmp directories for cache, sockets and pids. ### Miscellaneous @@ -641,17 +645,20 @@ $ cat config/database.yml # Configure Using Gemfile # gem 'pg' # -development: +default: &default adapter: postgresql encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default database: gitapp_development - pool: 5 - username: gitapp - password: ... ... ``` -It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. +It also generated some lines in our `database.yml` configuration corresponding to our choice of PostgreSQL for database. NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the `rails new` command to generate the basis of your app. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index b0334bfe4a..b1e472bb74 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -32,7 +32,7 @@ Configuring Rails Components In general, the work of configuring Rails means configuring the components of Rails, as well as configuring Rails itself. The configuration file `config/application.rb` and environment-specific configuration files (such as `config/environments/production.rb`) allow you to specify the various settings that you want to pass down to all of the components. -For example, the `config/application.rb` file includes this setting: +For example, you could add this setting to `config/application.rb` file: ```ruby config.time_zone = 'Central Time (US & Canada)' @@ -108,7 +108,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`). you don't want shown in the logs, such as passwords or credit card numbers. By default, Rails filters out passwords by adding `Rails.application.config.filter_parameters += [:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression. -* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. +* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://api.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. * `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you. @@ -138,7 +138,7 @@ defaults to `:debug` for all environments. The available log levels are: `:debug * `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to `true`. If `config.cache_classes` is `true`, this option is ignored. -* `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`. +* `secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get a random generated key in test and development environments, other environments should set one in `config/credentials.yml.enc`. * `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true.` Otherwise, you won't be able to use page caching and request for files that exist under the public directory. @@ -157,8 +157,6 @@ defaults to `:debug` for all environments. The available log levels are: `:debug * `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is set to `true` by default. -* `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`. - * `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem. * `config.assets.js_compressor` defines the JavaScript compressor to use. Possible values are `:closure`, `:uglifier` and `:yui` which require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems respectively. @@ -324,6 +322,10 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table. +* `config.active_record.internal_metadata_table_name` lets you set a string to be used as the name of the internal metadata table. + +* `config.active_record.protected_environments` lets you set an array of names of environments where destructive actions should be prohibited. + * `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to `true` (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table. * `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc`. @@ -350,9 +352,9 @@ All these configuration options are delegated to the `I18n` library. `config/environments/production.rb` which is generated by Rails. The default value is `true` if this configuration is not set. -* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling db:structure:dump. - The options are `:schema_search_path` (the default) which dumps any schemas listed in schema_search_path, - `:all` which always dumps all schemas regardless of the schema_search_path, +* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling `db:structure:dump`. + The options are `:schema_search_path` (the default) which dumps any schemas listed in `schema_search_path`, + `:all` which always dumps all schemas regardless of the `schema_search_path`, or a string of comma separated schemas. * `config.active_record.belongs_to_required_by_default` is a boolean value and @@ -362,19 +364,46 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.warn_on_records_fetched_greater_than` allows setting a warning threshold for query result size. If the number of records returned by a query exceeds the threshold, a warning is logged. This can be used to - identify queries which might be causing memory bloat. + identify queries which might be causing a memory bloat. * `config.active_record.index_nested_attribute_errors` allows errors for nested - has_many relationships to be displayed with an index as well as the error. + `has_many` relationships to be displayed with an index as well as the error. Defaults to `false`. +* `config.active_record.use_schema_cache_dump` enables users to get schema cache information + from `db/schema_cache.yml` (generated by `bin/rails db:schema:cache:dump`), instead of + having to send a query to the database to get this information. + Defaults to `true`. + 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: + +* `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 + ``` + The schema dumper adds one additional configuration option: -* `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless `config.active_record.schema_format == :ruby`. +* `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file. ### Configuring Action Controller @@ -398,6 +427,8 @@ The schema dumper adds one additional configuration option: * `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.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']`. * `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`. @@ -451,10 +482,23 @@ to `'http authentication'`. Defaults to `'signed cookie'`. * `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt -value. Defaults to `'encrypted cookie'`. + value. Defaults to `'encrypted cookie'`. * `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed -encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. + encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. + +* `config.action_dispatch.authenticated_encrypted_cookie_salt` sets the + authenticated encrypted cookie salt. Defaults to `'authenticated encrypted + cookie'`. + +* `config.action_dispatch.encrypted_cookie_cipher` sets the cipher to be + used for encrypted cookies. This defaults to `"aes-256-gcm"`. + +* `config.action_dispatch.signed_cookie_digest` sets the digest to be + used for signed cookies. This defaults to `"SHA1"`. + +* `config.action_dispatch.cookies_rotations` allows rotating + secrets, ciphers, and digests for encrypted and signed cookies. * `config.action_dispatch.perform_deep_munge` configures whether `deep_munge` method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation) @@ -488,8 +532,6 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. * `ActionDispatch::Callbacks.before` takes a block of code to run before the request. -* `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`. - * `ActionDispatch::Callbacks.after` takes a block of code to run after the request. ### Configuring Action View @@ -534,10 +576,14 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. error should be raised for missing translations. * `config.action_view.automatically_disable_submit_tag` determines whether - submit_tag should automatically disable on click, this defaults to `true`. + `submit_tag` should automatically disable on click, this defaults to `true`. * `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`. +* `config.action_view.form_with_generates_remote_forms` determines whether `form_with` generates remote forms or not. This defaults to `true`. + +* `config.action_view.form_with_generates_ids` determines whether `form_with` generates ids on inputs. This defaults to `true`. + ### Configuring Action Mailer There are a number of settings available on `config.action_mailer`: @@ -626,8 +672,6 @@ There are a few configuration options available in Active Support: * `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`. -* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns `false` (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `new_framework_defaults.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code. - * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. * `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations. @@ -935,7 +979,7 @@ By default Rails ships with three environments: "development", "test", and "prod Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server, just create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there. -That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc. +That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console -e staging`, `Rails.env.staging?` works, etc. ### Deploy to a subdirectory (relative url root) @@ -965,17 +1009,17 @@ Deploying your application using a reverse proxy has definite advantages over tr Many modern web servers can be used as a proxy server to balance third-party elements such as caching servers or application servers. -One such application server you can use is [Unicorn](http://unicorn.bogomips.org/) to run behind a reverse proxy. +One such application server you can use is [Unicorn](https://bogomips.org/unicorn/) to run behind a reverse proxy. In this case, you would need to configure the proxy server (NGINX, Apache, etc) to accept connections from your application server (Unicorn). By default Unicorn will listen for TCP connections on port 8080, but you can change the port or configure it to use sockets instead. -You can find more information in the [Unicorn readme](http://unicorn.bogomips.org/README.html) and understand the [philosophy](http://unicorn.bogomips.org/PHILOSOPHY.html) behind it. +You can find more information in the [Unicorn readme](https://bogomips.org/unicorn/README.html) and understand the [philosophy](https://bogomips.org/unicorn/PHILOSOPHY.html) behind it. Once you've configured the application server, you must proxy requests to it by configuring your web server appropriately. For example your NGINX config may include: ``` upstream application_server { - server 0.0.0.0:8080 + server 0.0.0.0:8080; } server { @@ -997,7 +1041,7 @@ server { } ``` -Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information. +Be sure to read the [NGINX documentation](https://nginx.org/en/docs/) for the most up-to-date information. Rails Environment Settings @@ -1019,7 +1063,7 @@ After loading the framework and any gems in your application, Rails turns to loa 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: If you have any ordering dependency in your initializers, you can control the load order through naming. Initializer files are loaded in alphabetical order by their path. For example, `01_critical.rb` will be loaded before `02_normal.rb`. +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. Initialization events --------------------- @@ -1183,7 +1227,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `finisher_hook`: Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines. -* `set_routes_reloader_hook`: Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`. +* `set_routes_reloader_hook`: Configures Action Dispatch to reload the routes file using `ActiveSupport::Callbacks.to_run`. * `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to `true`. @@ -1279,7 +1323,7 @@ know which pages it is allowed to index. Rails creates this file for you inside the `/public` folder. By default, it allows search engines to index all pages of your application. If you want to block -indexing on all pages of you application, use this: +indexing on all pages of your application, use this: ``` User-agent: * @@ -1305,7 +1349,7 @@ end Otherwise, in every request Rails walks the application tree to check if anything has changed. -On Linux and Mac OS X no additional gems are needed, but some are required +On Linux and macOS no additional gems are needed, but some are required [for *BSD](https://github.com/guard/listen#on-bsd) and [for Windows](https://github.com/guard/listen#on-windows). diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index fe5437ae5d..967c992c05 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -13,9 +13,9 @@ After reading this guide, you will know: * How to contribute to the Ruby on Rails documentation. * How to contribute to the Ruby on Rails code. -Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. +Ruby on Rails is not "someone else's framework." Over the years, thousands of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. -As mentioned in [Rails +As mentioned in [Rails' README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/). -------------------------------------------------------------------------------- @@ -84,7 +84,9 @@ discussions new features require. Helping to Resolve Existing Issues ---------------------------------- -As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [issues list](https://github.com/rails/rails/issues) in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: +As a next step beyond reporting issues, you can help the core team resolve existing ones by providing feedback about them. If you are new to Rails core development, that might be a great way to walk your first steps, you'll get familiar with the code base and the processes. + +If you check the [issues list](https://github.com/rails/rails/issues) in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: ### Verifying Bug Reports @@ -92,19 +94,19 @@ For starters, it helps just to verify bug reports. Can you reproduce the reporte If an issue is very vague, can you help narrow it down to something more specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem. -If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section. +If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "[Contributing to the Rails Code](#contributing-to-the-rails-code)" section. Anything you can do to make bug reports more succinct or easier to reproduce helps folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. ### Testing Patches -You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need first to create a dedicated branch: +You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. In order to apply someone's changes, you need to first create a dedicated branch: ```bash $ git checkout -b testing_branch ``` -Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. +Then, you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. ```bash $ git remote add JohnSmith https://github.com/JohnSmith/rails.git @@ -132,35 +134,24 @@ learn about Ruby on Rails, and the API, which serves as a reference. You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails. -You can either open a pull request to [Rails](https://github.com/rails/rails) or -ask the [Rails core team](http://rubyonrails.org/community/#core) for commit access on -docrails if you contribute regularly. -Please do not open pull requests in docrails, if you'd like to get feedback on your -change, ask for it in [Rails](https://github.com/rails/rails) instead. - -Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. - -If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub. +To do so, open a pull request to [Rails](https://github.com/rails/rails) on GitHub. When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html). -NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements. - NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. -WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. - Translating Rails Guides ------------------------ -We are happy to have people volunteer to translate the Rails guides into their own language. -If you want to translate the Rails guides in your own language, follows these steps: +We are happy to have people volunteer to translate the Rails guides. Just follow these steps: -* Fork the project (rails/rails). +* Fork https://github.com/rails/rails. * Add a source folder for your own language, for example: *guides/source/it-IT* for Italian. * Copy the contents of *guides/source* into your own language directory and translate them. * Do NOT translate the HTML files, as they are automatically generated. +Note that translations are not submitted to the Rails repository. As detailed above, your work happens in a fork. This is so because in practice documentation maintenance via patches is only sustainable in English. + To generate the guides in HTML format cd into the *guides* directory then run (eg. for it-IT): ```bash @@ -175,11 +166,11 @@ NOTE: The instructions are for Rails > 4. The Redcarpet Gem doesn't work with JR Translation efforts we know about (various versions): * **Italian**: [https://github.com/rixlabs/docrails](https://github.com/rixlabs/docrails) -* **Spanish**: [http://wiki.github.com/gramos/docrails](http://wiki.github.com/gramos/docrails) -* **Polish**: [https://github.com/apohllo/docrails/tree/master](https://github.com/apohllo/docrails/tree/master) +* **Spanish**: [https://github.com/gramos/docrails/wiki](https://github.com/gramos/docrails/wiki) +* **Polish**: [https://github.com/apohllo/docrails](https://github.com/apohllo/docrails) * **French** : [https://github.com/railsfrance/docrails](https://github.com/railsfrance/docrails) * **Czech** : [https://github.com/rubyonrails-cz/docrails/tree/czech](https://github.com/rubyonrails-cz/docrails/tree/czech) -* **Turkish** : [https://github.com/ujk/docrails/tree/master](https://github.com/ujk/docrails/tree/master) +* **Turkish** : [https://github.com/ujk/docrails](https://github.com/ujk/docrails) * **Korean** : [https://github.com/rorlakr/rails-guides](https://github.com/rorlakr/rails-guides) * **Simplified Chinese** : [https://github.com/ruby-china/guides](https://github.com/ruby-china/guides) * **Traditional Chinese** : [https://github.com/docrails-tw/guides](https://github.com/docrails-tw/guides) @@ -195,7 +186,7 @@ To move on from submitting bugs to helping resolve existing issues or contributi #### The Easy Way -The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box). +The easiest and recommended way to get a development environment ready to hack is to use the [rails-dev-box](https://github.com/rails/rails-dev-box). #### The Hard Way @@ -335,7 +326,7 @@ file. #### Testing Active Record -First, create the databases you'll need. You can find a list of the required +First, create the databases you'll need. You can find a list of the required table names, usernames, and passwords in `activerecord/test/config.example.yml`. For MySQL and PostgreSQL, running the SQL statements `create database @@ -418,16 +409,6 @@ examples or multiple paragraphs. Otherwise, it's best to make a new paragraph. Some changes require the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes. -### Sanity Check - -You should not be the only person who looks at the code before you submit it. -If you know someone else who uses Rails, try asking them if they'll check out -your work. If you don't know anyone else using Rails, try hopping into the IRC -room or posting about your idea to the rails-core mailing list. Doing this in -private before you push a patch out publicly is the "smoke test" for a patch: -if you can't convince one other developer of the beauty of your code, you’re -unlikely to convince the core team either. - ### Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to Git: @@ -501,7 +482,7 @@ Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and pr Add the new remote to your local repository on your local machine: ```bash -$ git remote add mine https://github.com:<your user name>/rails.git +$ git remote add mine https://github.com/<your user name>/rails.git ``` Push to your remote: @@ -575,7 +556,7 @@ is the open source life. If it's been over a week, and you haven't heard anything, you might want to try and nudge things along. You can use the [rubyonrails-core mailing -list](http://groups.google.com/group/rubyonrails-core/) for this. You can also +list](https://groups.google.com/forum/#!forum/rubyonrails-core) for this. You can also leave another comment on the pull request. While you're waiting for feedback on your pull request, open up a few other @@ -685,4 +666,4 @@ And then... think about your next contribution! Rails Contributors ------------------ -All contributions, either via master or docrails, get credit in [Rails Contributors](http://contributors.rubyonrails.org). +All contributions get credit in [Rails Contributors](http://contributors.rubyonrails.org). diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index ba0cdbf3af..07c78be3db 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -162,41 +162,41 @@ class ArticlesController < ApplicationController # ... def create - @article = Article.new(params[:article]) + @article = Article.new(article_params) logger.debug "New article: #{@article.attributes.inspect}" logger.debug "Article should be valid: #{@article.valid?}" if @article.save - flash[:notice] = 'Article was successfully created.' logger.debug "The article was saved and now the user is going to be redirected..." - redirect_to(@article) + redirect_to @article, notice: 'Article was successfully created.' else - render action: "new" + render :new end end # ... + + private + def article_params + params.require(:article).permit(:title, :body, :published) + end end ``` Here's an example of the log generated when this controller action is executed: ``` -Processing ArticlesController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST] - Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl -vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4 - Parameters: {"commit"=>"Create", "article"=>{"title"=>"Debugging Rails", - "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, - "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"articles"} -New article: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", - "published"=>false, "created_at"=>nil} +Started POST "/articles" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 +Processing by ArticlesController#create as HTML + Parameters: {"utf8"=>"✓", "authenticity_token"=>"xhuIbSBFytHCE1agHgvrlKnSVIOGD6jltW2tO+P6a/ACjQ3igjpV4OdbsZjIhC98QizWH9YdKokrqxBCJrtoqQ==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, "commit"=>"Create Article"} +New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>false, "created_at"=>nil, "updated_at"=>nil} Article should be valid: true - Article Create (0.000443) INSERT INTO "articles" ("updated_at", "title", "body", "published", - "created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails', - 'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54') + (0.1ms) BEGIN + SQL (0.4ms) INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs!!!"], ["published", "f"], ["created_at", "2017-08-20 11:53:10.010435"], ["updated_at", "2017-08-20 11:53:10.010435"]] + (0.3ms) COMMIT The article was saved and now the user is going to be redirected... -Redirected to # Article:0x20af760> -Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/articles] +Redirected to http://localhost:3000/articles/1 +Completed 302 Found in 4ms (ActiveRecord: 0.8ms) ``` Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia. @@ -313,7 +313,7 @@ For example: ```bash => Booting Puma -=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000 +=> Rails 5.1.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl @@ -401,7 +401,7 @@ To see the previous ten lines you should type `list-` (or `l-`). 7 byebug 8 @articles = Article.find_recent 9 - 10 respond_to do |format| + 10 respond_to do |format| ``` This way you can move inside the file and see the code above the line where you @@ -445,11 +445,11 @@ then `backtrace` will supply the answer. --> #0 ArticlesController.index at /PathToProject/app/controllers/articles_controller.rb:8 #1 ActionController::BasicImplicitRender.send_action(method#String, *args#Array) - at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/basic_implicit_render.rb:4 + at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/basic_implicit_render.rb:4 #2 AbstractController::Base.process_action(action#NilClass, *args#Array) - at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:181 + at /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb:181 #3 ActionController::Rendering.process_action(action, *args) - at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:30 + at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/rendering.rb:30 ... ``` @@ -461,7 +461,7 @@ context. ``` (byebug) frame 2 -[176, 185] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb +[176, 185] in /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb 176: # is the intended way to override action dispatching. 177: # 178: # Notice that the first argument is the method to be dispatched @@ -540,8 +540,8 @@ command later in this guide). 7 byebug 8 @articles = Article.find_recent 9 -=> 10 respond_to do |format| - 11 format.html # index.html.erb +=> 10 respond_to do |format| + 11 format.html # index.html.erb 12 format.json { render json: @articles } 13 end 14 end @@ -606,7 +606,6 @@ You can also inspect for an object method this way: @new_record = true @readonly = false @transaction_state = nil -@txn = nil ``` You can also use `display` to start watching variables. This is a good way of @@ -677,7 +676,7 @@ Ruby instruction to be executed -- in this case, Active Support's `week` method. ``` (byebug) step -[49, 58] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb +[49, 58] in /PathToGems/activesupport-5.1.0/lib/active_support/core_ext/numeric/time.rb 49: 50: # Returns a Duration instance matching the number of weeks provided. 51: # diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 16c7e782bc..50274d700b 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -21,24 +21,25 @@ The easiest and recommended way to get a development environment ready to hack i The Hard Way ------------ -In case you can't use the Rails development box, see section below, these are the steps to manually build a development box for Ruby on Rails core development. +In case you can't use the Rails development box, see the steps below to manually +build a development box for Ruby on Rails core development. ### Install Git -Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: +Ruby on Rails uses Git for source code control. The [Git homepage](https://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: -* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. -* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git. -* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. -* [GitHub](http://help.github.com) offers links to a variety of Git resources. -* [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. +* [Try Git course](https://try.github.io/) is an interactive course that will teach you the basics. +* The [official Documentation](https://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git. +* [Everyday Git](https://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. +* [GitHub](https://help.github.com/) offers links to a variety of Git resources. +* [Pro Git](https://git-scm.com/book) is an entire book about Git with a Creative Commons license. ### Clone the Ruby on Rails Repository Navigate to the folder where you want the Ruby on Rails source code (it will create its own `rails` subdirectory) and run: ```bash -$ git clone git://github.com/rails/rails.git +$ git clone https://github.com/rails/rails.git $ cd rails ``` @@ -46,7 +47,7 @@ $ cd rails 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. -Install first SQLite3 and its development files for the `sqlite3` gem. Mac OS X +Install first SQLite3 and its development files for the `sqlite3` gem. On macOS users are done with: ```bash @@ -62,7 +63,7 @@ $ sudo apt-get install sqlite3 libsqlite3-dev If you are on Fedora or CentOS, you're done with ```bash -$ sudo yum install sqlite3 sqlite3-devel +$ sudo yum install libsqlite3x libsqlite3x-devel ``` If you are on Arch Linux, you will need to run: @@ -79,7 +80,7 @@ For FreeBSD users, you're done with: Or compile the `databases/sqlite3` port. -Get a recent version of [Bundler](http://bundler.io/) +Get a recent version of [Bundler](https://bundler.io/) ```bash $ gem install bundler @@ -96,7 +97,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. -You can use [Homebrew](http://brew.sh/) to install memcached on OS X: +You can use [Homebrew](https://brew.sh/) to install memcached on macOS: ```bash $ brew install memcached @@ -181,7 +182,7 @@ The Active Record test suite requires a custom config file: `activerecord/test/c 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 OS X, you can run: +On macOS, you can run: ```bash $ brew install mysql @@ -190,7 +191,7 @@ $ brew install postgresql Follow the instructions given by Homebrew to start these. -In Ubuntu just run: +On Ubuntu, just run: ```bash $ sudo apt-get install mysql-server libmysqlclient-dev @@ -260,34 +261,34 @@ with your development account, on Linux or BSD, you just have to run: $ sudo -u postgres createuser --superuser $USER ``` -and for OS X: +and for macOS: ```bash $ createuser --superuser $USER ``` -Then you need to create the test databases with +Then, you need to create the test databases with: ```bash $ cd activerecord $ bundle exec rake db:postgresql:build ``` -It is possible to build databases for both PostgreSQL and MySQL with +It is possible to build databases for both PostgreSQL and MySQL with: ```bash $ cd activerecord $ bundle exec rake db:create ``` -You can cleanup the databases using +You can cleanup the databases using: ```bash $ cd activerecord $ 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: 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". @@ -299,11 +300,11 @@ Action Cable uses Redis as its default subscriptions adapter ([read more](action #### Install Redis From Source -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](http://redis.io/download#installation). +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 OS X, you can run: +On macOS, you can run: ```bash $ brew install redis @@ -311,7 +312,7 @@ $ brew install redis Follow the instructions given by Homebrew to start these. -In Ubuntu just run: +On Ubuntu, just run: ```bash $ sudo apt-get install redis-server @@ -323,7 +324,7 @@ On Fedora or CentOS (requires EPEL enabled), just run: $ sudo yum install redis ``` -If you are running Arch Linux just run: +If you are running Arch Linux, just run: ```bash $ sudo pacman -S redis @@ -335,3 +336,43 @@ 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: + +```bash +yarn install +``` diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 2925fb4b58..5cddf79eeb 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -72,7 +72,7 @@ url: active_support_core_extensions.html description: This guide documents the Ruby core extensions defined in Active Support. - - name: Rails Internationalization API + name: Rails Internationalization (I18n) API url: i18n.html description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country, and so on. - @@ -84,6 +84,10 @@ url: active_job_basics.html description: This guide provides you with all you need to get started creating, enqueuing, and executing background jobs. - + name: Active Storage Overview + url: active_storage_overview.html + description: This guide covers how to attach files to your Active Record models. + - name: Testing Rails Applications url: testing.html description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to Integration Testing. Enjoy. @@ -104,7 +108,7 @@ url: command_line.html description: This guide covers the command line tools provided by Rails. - - name: Asset Pipeline + name: The Asset Pipeline url: asset_pipeline.html description: This guide documents the asset pipeline. - @@ -130,11 +134,6 @@ url: active_support_instrumentation.html description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code. - - name: Profiling Rails Applications - work_in_progress: true - url: profiling.html - description: This guide explains how to profile your Rails applications to improve performance. - - name: Using Rails for API-only Applications url: api_app.html description: This guide explains how to effectively use Rails to develop a JSON API application. @@ -156,7 +155,7 @@ url: rails_on_rack.html description: This guide covers Rails integration with Rack and interfacing with other Rack components. - - name: Creating and Customizing Rails Generators + name: Creating and Customizing Rails Generators & Templates url: generators.html description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator). - @@ -164,6 +163,11 @@ url: engines.html description: This guide explains how to write a mountable engine. work_in_progress: true + - + name: Threading and Code Execution in Rails + url: threading_and_code_execution.html + description: This guide describes the considerations needed and tools available when working directly with concurrency in a Rails application. + work_in_progress: true - name: Contributing to Ruby on Rails documents: @@ -183,7 +187,7 @@ name: Maintenance Policy documents: - - name: Maintenance Policy + name: Maintenance Policy for Ruby on Rails url: maintenance_policy.html description: What versions of Ruby on Rails are currently supported, and when to expect new versions. - @@ -194,6 +198,14 @@ url: upgrading_ruby_on_rails.html description: This guide helps in upgrading applications to latest Ruby on Rails versions. - + name: Ruby on Rails 5.2 Release Notes + url: 5_2_release_notes.html + description: Release notes for Rails 5.2. + - + name: Ruby on Rails 5.1 Release Notes + url: 5_1_release_notes.html + description: Release notes for Rails 5.1. + - name: Ruby on Rails 5.0 Release Notes url: 5_0_release_notes.html description: Release notes for Rails 5.0. diff --git a/guides/source/engines.md b/guides/source/engines.md index 0020112a1c..8d81296fa5 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -14,6 +14,7 @@ After reading this guide, you will know: * How to build features for the engine. * How to hook the engine into an application. * How to override engine functionality in the application. +* Avoid loading Rails frameworks with Load and Configuration Hooks -------------------------------------------------------------------------------- @@ -59,10 +60,10 @@ only be enhancing it, rather than changing it drastically. To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or -[Forem](https://github.com/radar/forem), an engine that provides forum +[Thredded](https://github.com/thredded/thredded), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and -[RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine. +[Refinery CMS](https://github.com/refinery/refinerycms), a CMS engine. Finally, engines would not have been possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever @@ -345,6 +346,9 @@ invoke test_unit create test/controllers/blorgh/articles_controller_test.rb invoke helper create app/helpers/blorgh/articles_helper.rb +invoke test_unit +create test/application_system_test_case.rb +create test/system/articles_test.rb invoke assets invoke js create app/assets/javascripts/blorgh/articles.js @@ -533,12 +537,12 @@ directory at `app/views/blorgh/comments` and in it a new file called ```html+erb <h3>New comment</h3> -<%= form_for [@article, @article.comments.build] do |f| %> +<%= form_with(model: [@article, @article.comments.build], local: true) do |form| %> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> - <%= f.submit %> + <%= form.submit %> <% end %> ``` @@ -649,7 +653,7 @@ there isn't an application handy to test this out in, generate one using the $ rails new unicorn ``` -Usually, specifying the engine inside the Gemfile would be done by specifying it +Usually, specifying the engine inside the `Gemfile` would be done by specifying it as a normal, everyday gem. ```ruby @@ -779,8 +783,8 @@ added above the `title` field with this code: ```html+erb <div class="field"> - <%= f.label :author_name %><br> - <%= f.text_field :author_name %> + <%= form.label :author_name %><br> + <%= form.text_field :author_name %> </div> ``` @@ -917,7 +921,7 @@ engine: mattr_accessor :author_class ``` -This method works like its brothers, `attr_accessor` and `cattr_accessor`, but +This method works like its siblings, `attr_accessor` and `cattr_accessor`, but provides a setter and getter method on the module with the specified name. To use it, it must be referenced using `Blorgh.author_class`. @@ -978,7 +982,7 @@ Blorgh.author_class = "User" WARNING: It's very important here to use the `String` version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table. This could lead to -problems if the table wasn't already existing. Therefore, a `String` should be +problems if the table didn't already exist. Therefore, a `String` should be used and then converted to a class using `constantize` in the engine later on. Go ahead and try to create a new article. You will see that it works exactly in the @@ -1318,7 +1322,7 @@ engine. Assets within an engine work in an identical way to a full application. Because the engine class inherits from `Rails::Engine`, the application will know to -look up assets in the engine's 'app/assets' and 'lib/assets' directories. +look up assets in the engine's `app/assets` and `lib/assets` directories. Like all of the other components of an engine, the assets should be namespaced. This means that if you have an asset called `style.css`, it should be placed at @@ -1357,7 +1361,7 @@ that only exists for your engine. In this case, the host application doesn't need to require `admin.css` or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorgh/admin.css"` in its stylesheets. In this situation, you should -explicitly define these assets for precompilation. This tells sprockets to add +explicitly define these assets for precompilation. This tells Sprockets to add your engine assets when `bin/rails assets:precompile` is triggered. You can define assets for precompilation in `engine.rb`: @@ -1410,3 +1414,115 @@ module MyEngine end end ``` + +Active Support On Load Hooks +---------------------------- + +Active Support is the Ruby on Rails component responsible for providing Ruby language extensions, utilities, and other transversal utilities. + +Rails code can often be referenced on load of an application. Rails is responsible for the load order of these frameworks, so when you load frameworks, such as `ActiveRecord::Base`, prematurely you are violating an implicit contract your application has with Rails. Moreover, by loading code such as `ActiveRecord::Base` on boot of your application you are loading entire frameworks which may slow down your boot time and could cause conflicts with load order and boot of your application. + +On Load hooks are the API that allow you to hook into this initialization process without violating the load contract with Rails. This will also mitigate boot performance degradation and avoid conflicts. + +## What are `on_load` hooks? + +Since Ruby is a dynamic language, some code will cause different Rails frameworks to load. Take this snippet for instance: + +```ruby +ActiveRecord::Base.include(MyActiveRecordHelper) +``` + +This snippet means that when this file is loaded, it will encounter `ActiveRecord::Base`. This encounter causes Ruby to look for the definition of that constant and will require it. This causes the entire Active Record framework to be loaded on boot. + +`ActiveSupport.on_load` is a mechanism that can be used to defer the loading of code until it is actually needed. The snippet above can be changed to: + +```ruby +ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper } +``` + +This new snippet will only include `MyActiveRecordHelper` when `ActiveRecord::Base` is loaded. + +## How does it work? + +In the Rails framework these hooks are called when a specific library is loaded. For example, when `ActionController::Base` is loaded, the `:action_controller_base` hook is called. This means that all `ActiveSupport.on_load` calls with `:action_controller_base` hooks will be called in the context of `ActionController::Base` (that means `self` will be an `ActionController::Base`). + +## Modifying code to use `on_load` hooks + +Modifying code is generally straightforward. If you have a line of code that refers to a Rails framework such as `ActiveRecord::Base` you can wrap that code in an `on_load` hook. + +### Example 1 + +```ruby +ActiveRecord::Base.include(MyActiveRecordHelper) +``` + +becomes + +```ruby +ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper } # self refers to ActiveRecord::Base here, so we can simply #include +``` + +### Example 2 + +```ruby +ActionController::Base.prepend(MyActionControllerHelper) +``` + +becomes + +```ruby +ActiveSupport.on_load(:action_controller_base) { prepend MyActionControllerHelper } # self refers to ActionController::Base here, so we can simply #prepend +``` + +### Example 3 + +```ruby +ActiveRecord::Base.include_root_in_json = true +``` + +becomes + +```ruby +ActiveSupport.on_load(:active_record) { self.include_root_in_json = true } # self refers to ActiveRecord::Base here +``` + +## Available Hooks + +These are the hooks you can use in your own code. + +To hook into the initialization process of one of the following classes use the available hook. + +| Class | Available Hooks | +| --------------------------------- | ------------------------------------ | +| `ActionCable` | `action_cable` | +| `ActionController::API` | `action_controller_api` | +| `ActionController::API` | `action_controller` | +| `ActionController::Base` | `action_controller_base` | +| `ActionController::Base` | `action_controller` | +| `ActionController::TestCase` | `action_controller_test_case` | +| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` | +| `ActionDispatch::SystemTestCase` | `action_dispatch_system_test_case` | +| `ActionMailer::Base` | `action_mailer` | +| `ActionMailer::TestCase` | `action_mailer_test_case` | +| `ActionView::Base` | `action_view` | +| `ActionView::TestCase` | `action_view_test_case` | +| `ActiveJob::Base` | `active_job` | +| `ActiveJob::TestCase` | `active_job_test_case` | +| `ActiveRecord::Base` | `active_record` | +| `ActiveSupport::TestCase` | `active_support_test_case` | +| `i18n` | `i18n` | + +## Configuration hooks + +These are the available configuration hooks. They do not hook into any particular framework, but instead they run in context of the entire application. + +| Hook | Use Case | +| ---------------------- | ---------------------------------------------------------------------------------- | +| `before_configuration` | First configurable block to run. Called before any initializers are run. | +| `before_initialize` | Second configurable block to run. Called before frameworks initialize. | +| `before_eager_load` | Third configurable block to run. Does not run if `config.eager_load` set to false. | +| `after_initialize` | Last configurable block to run. Called after frameworks initialize. | + +### Example + +`config.before_configuration { puts 'I am called before any initializers' }` diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 8ad76ad01e..53c567727f 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -1,7 +1,7 @@ **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** -Form Helpers -============ +Action View Form Helpers +======================== Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of the need to handle form control naming and its numerous attributes. Rails does away with this complexity by providing view helpers for generating form markup. However, since these helpers have different use cases, developers need to know the differences between the helper methods before putting them to use. @@ -164,7 +164,7 @@ make it easier for users to click the inputs. Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, -color fields, datetime fields, datetime-local fields, month fields, week fields, +color fields, datetime-local fields, month fields, week fields, URL fields, email fields, number fields and range fields: ```erb @@ -274,10 +274,12 @@ There are a few things to note here: The resulting HTML is: ```html -<form accept-charset="UTF-8" action="/articles" method="post" class="nifty_form"> - <input id="article_title" name="article[title]" type="text" /> - <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea> - <input name="commit" type="submit" value="Create" /> +<form class="nifty_form" id="new_article" action="/articles" accept-charset="UTF-8" method="post"> + <input name="utf8" type="hidden" value="✓" /> + <input type="hidden" name="authenticity_token" value="NRkFyRWxdYNfUg7vYxLOp2SLf93lvnl+QwDWorR42Dp6yZXPhHEb6arhDOIWcqGit8jfnrPwL781/xlrzj63TA==" /> + <input type="text" name="article[title]" id="article_title" /> + <textarea name="article[body]" id="article_body" cols="60" rows="12"></textarea> + <input type="submit" name="commit" value="Create" data-disable-with="Create" /> </form> ``` @@ -299,9 +301,11 @@ You can create a similar binding without actually creating `<form>` tags with th which produces the following output: ```html -<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> - <input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" /> +<form class="new_person" id="new_person" action="/people" accept-charset="UTF-8" method="post"> + <input name="utf8" type="hidden" value="✓" /> + <input type="hidden" name="authenticity_token" value="bL13x72pldyDD8bgtkjKQakJCpd4A8JdXGbfksxBDHdf1uC0kCMqe2tvVdUYfidJt0fj3ihC4NxiVHv8GVYxJA==" /> + <input type="text" name="person[name]" id="person_name" /> + <input type="text" name="contact_detail[phone_number]" id="contact_detail_phone_number" /> </form> ``` @@ -531,7 +535,7 @@ To leverage time zone support in Rails, you have to ask your users what time zon <%= time_zone_select(:person, :time_zone) %> ``` -There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the API documentation to learn about the possible arguments for these two methods. +There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-time_zone_options_for_select) to learn about the possible arguments for these two methods. Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/stefanpenner/country_select). When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails). @@ -877,7 +881,7 @@ Active Record provides model level support via the `accepts_nested_attributes_fo ```ruby class Person < ApplicationRecord - has_many :addresses + has_many :addresses, inverse_of: :person accepts_nested_attributes_for :addresses end @@ -916,7 +920,7 @@ When an association accepts nested attributes `fields_for` renders its block onc ```ruby def new @person = Person.new - 2.times { @person.addresses.build} + 2.times { @person.addresses.build } end ``` diff --git a/guides/source/generators.md b/guides/source/generators.md index d0b6cef3fd..b7b8262e4a 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -90,13 +90,15 @@ $ bin/rails generate generator initializer create lib/generators/initializer/initializer_generator.rb create lib/generators/initializer/USAGE create lib/generators/initializer/templates + invoke test_unit + create test/lib/generators/initializer_generator_test.rb ``` This is the generator just created: ```ruby class InitializerGenerator < Rails::Generators::NamedBase - source_root File.expand_path("../templates", __FILE__) + source_root File.expand_path('templates', __dir__) end ``` @@ -122,7 +124,7 @@ And now let's change the generator to copy this template when invoked: ```ruby class InitializerGenerator < Rails::Generators::NamedBase - source_root File.expand_path("../templates", __FILE__) + source_root File.expand_path('templates', __dir__) def copy_initializer_file copy_file "initializer.rb", "config/initializers/#{file_name}.rb" @@ -197,6 +199,9 @@ $ bin/rails generate scaffold User name:string invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder + invoke test_unit + create test/application_system_test_case.rb + create test/system/users_test.rb invoke assets invoke coffee create app/assets/javascripts/users.coffee @@ -238,6 +243,8 @@ $ bin/rails generate generator rails/my_helper create lib/generators/rails/my_helper/my_helper_generator.rb create lib/generators/rails/my_helper/USAGE create lib/generators/rails/my_helper/templates + invoke test_unit + create test/lib/generators/rails/my_helper_generator_test.rb ``` After that, we can delete both the `templates` directory and the `source_root` @@ -415,6 +422,9 @@ $ bin/rails generate scaffold Comment body:text invoke jbuilder create app/views/comments/index.json.jbuilder create app/views/comments/show.json.jbuilder + invoke test_unit + create test/application_system_test_case.rb + create test/system/comments_test.rb invoke assets invoke coffee create app/assets/javascripts/comments.coffee @@ -426,7 +436,7 @@ Fallbacks allow your generators to have a single responsibility, increasing code Application Templates --------------------- -Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html). +Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred to as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html). ```ruby gem "rspec-rails", group: "test" @@ -504,13 +514,13 @@ Available options are: Any additional options passed to this method are put on the end of the line: ```ruby -gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" +gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master" ``` The above code will put the following line into `Gemfile`: ```ruby -gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" +gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master" ``` ### `gem_group` @@ -627,7 +637,7 @@ This method also takes a block: ```ruby lib "super_special.rb" do - puts "Super special!" + "puts 'Super special!'" end ``` @@ -636,7 +646,7 @@ end Creates a Rake file in the `lib/tasks` directory of the application. ```ruby -rakefile "test.rake", "hello there" +rakefile "test.rake", 'task(:hello) { puts "Hello, there" }' ``` This method also takes a block: @@ -689,14 +699,6 @@ Available options are: * `:env` - Specifies the environment in which to run this rake task. * `:sudo` - Whether or not to run this task using `sudo`. Defaults to `false`. -### `capify!` - -Runs the `capify` command from Capistrano at the root of the application which generates Capistrano configuration. - -```ruby -capify! -``` - ### `route` Adds text to the `config/routes.rb` file: diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 8a451ab793..b007baea87 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -20,16 +20,7 @@ Guide Assumptions This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience -with Rails. However, to get the most out of it, you need to have some -prerequisites installed: - -* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer. -* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you - are using Windows. -* The [RubyGems](https://rubygems.org) packaging system, which is installed with - Ruby by default. To learn more about RubyGems, please read the - [RubyGems Guides](http://guides.rubygems.org). -* A working installation of the [SQLite3 Database](https://www.sqlite.org). +with Rails. Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning @@ -46,7 +37,7 @@ development with Rails. What is Rails? -------------- -Rails is a web application development framework written in the Ruby language. +Rails is a web application development framework written in the Ruby programming language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. @@ -86,7 +77,10 @@ your prompt will look something like `c:\source_code>` ### Installing Rails -Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose +Before you install Rails, you should check to make sure that your system has the +proper prerequisites installed. These include Ruby and SQLite3. + +Open up a command line prompt. On macOS open Terminal.app, on Windows choose "Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed: @@ -96,12 +90,19 @@ $ ruby -v ruby 2.3.1p112 ``` +Rails requires Ruby version 2.2.2 or later. If the version number returned is +less than that number, you'll need to install a fresh copy of Ruby. + TIP: A number of tools exist to help you quickly install Ruby and Ruby on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), -while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp). +while macOS users can use [Tokaido](https://github.com/tokaido/tokaidoapp). For more installation methods for most Operating Systems take a look at [ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). +If you are working on Windows, you should also install the +[Ruby Installer Development Kit](http://rubyinstaller.org/downloads/). + +You will also need an installation of the SQLite3 database. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. On Windows, if you installed Rails through Rails Installer, you already have SQLite installed. Others can find installation instructions @@ -127,7 +128,7 @@ run the following: $ rails --version ``` -If it says something like "Rails 5.0.0", you are ready to continue. +If it says something like "Rails 5.1.1", you are ready to continue. ### Creating the Blog Application @@ -171,18 +172,20 @@ of the files and folders that Rails created by default: |app/|Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You'll focus on this folder for the remainder of this guide.| |bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.| |config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| -|config.ru|Rack configuration for Rack based servers used to start the application.| +|config.ru|Rack configuration for Rack based servers used to start the application. For more information about Rack, see the [Rack website](https://rack.github.io/).| |db/|Contains your current database schema, as well as the database migrations.| -|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](http://bundler.io).| +|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](https://bundler.io).| |lib/|Extended modules for your application.| |log/|Application log files.| +|package.json|This file allows you to specify what npm dependencies are needed for your Rails application. This file is used by Yarn. For more information about Yarn, see the [Yarn website](https://yarnpkg.com/lang/en/).| |public/|The only folder seen by the world as-is. Contains static files and compiled assets.| -|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.| +|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing `Rakefile`, you should add your own tasks by adding files to the `lib/tasks` directory of your application.| |README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| |tmp/|Temporary files (like cache and pid files).| |vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.| -|.gitignore|This file tells git which files (or patterns) it should ignore. See [Github - Ignoring files](https://help.github.com/articles/ignoring-files) for more info about ignoring files. +|.gitignore|This file tells git which files (or patterns) it should ignore. See [GitHub - Ignoring files](https://help.github.com/articles/ignoring-files) for more info about ignoring files. +|.ruby-version|This file contains the default Ruby version.| Hello, Rails! ------------- @@ -206,8 +209,8 @@ folder directly to the Ruby interpreter e.g. `ruby bin\rails server`. TIP: Compiling CoffeeScript and JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence of a runtime you will see an `execjs` error during asset compilation. -Usually Mac OS X and Windows come with a JavaScript runtime installed. -Rails adds the `therubyracer` gem to the generated `Gemfile` in a +Usually macOS and Windows come with a JavaScript runtime installed. +Rails adds the `mini_racer` gem to the generated `Gemfile` in a commented line for new apps and you can uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby users and is added by default to the `Gemfile` in apps generated under JRuby. You can investigate @@ -221,7 +224,7 @@ your application in action, open a browser window and navigate to TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt -cursor again. For most UNIX-like systems including Mac OS X this will be a +cursor again. For most UNIX-like systems including macOS this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server. @@ -308,7 +311,7 @@ end ``` This is your application's _routing file_ which holds entries in a special -[DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language) +[DSL (domain-specific language)](https://en.wikipedia.org/wiki/Domain-specific_language) that tells Rails how to connect incoming requests to controllers and actions. Edit this file by adding the line of code `root 'welcome#index'`. @@ -369,16 +372,17 @@ singular form `article` and makes meaningful use of the distinction. ```bash $ bin/rails routes - Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index + Prefix Verb URI Pattern Controller#Action +welcome_index GET /welcome/index(.:format) welcome#index + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new + edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index ``` In the next section, you will add the ability to create new articles in your @@ -508,23 +512,23 @@ harmoniously! It's time to create the form for a new article. To create a form within this template, you will use a *form builder*. The primary form builder for Rails is provided by a helper -method called `form_for`. To use this method, add this code into +method called `form_with`. To use this method, add this code into `app/views/articles/new.html.erb`: ```html+erb -<%= form_for :article do |f| %> +<%= form_with scope: :article, local: true do |form| %> <p> - <%= f.label :title %><br> - <%= f.text_field :title %> + <%= form.label :title %><br> + <%= form.text_field :title %> </p> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> ``` @@ -532,12 +536,12 @@ method called `form_for`. To use this method, add this code into If you refresh the page now, you'll see the exact same form from our example above. Building forms in Rails is really just that easy! -When you call `form_for`, you pass it an identifying object for this -form. In this case, it's the symbol `:article`. This tells the `form_for` +When you call `form_with`, you pass it an identifying scope for this +form. In this case, it's the symbol `:article`. This tells the `form_with` helper what this form is for. Inside the block for this method, the -`FormBuilder` object - represented by `f` - is used to build two labels and two +`FormBuilder` object - represented by `form` - is used to build two labels and two text fields, one each for the title and text of an article. Finally, a call to -`submit` on the `f` object will create a submit button for the form. +`submit` on the `form` object will create a submit button for the form. There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the `action` @@ -546,15 +550,15 @@ this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new article. The form needs to use a different URL in order to go somewhere else. -This can be done quite simply with the `:url` option of `form_for`. +This can be done quite simply with the `:url` option of `form_with`. Typically in Rails, the action that is used for new form submissions like this is called "create", and so the form should be pointed to that action. -Edit the `form_for` line inside `app/views/articles/new.html.erb` to look like +Edit the `form_with` line inside `app/views/articles/new.html.erb` to look like this: ```html+erb -<%= form_for :article, url: articles_path do |f| %> +<%= form_with scope: :article, url: articles_path, local: true do |form| %> ``` In this example, the `articles_path` helper is passed to the `:url` option. @@ -564,15 +568,16 @@ To see what Rails will do with this, we look back at the output of ```bash $ bin/rails routes Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index +welcome_index GET /welcome/index(.:format) welcome#index + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new + edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index ``` The `articles_path` helper tells Rails to point the form to the URI Pattern @@ -591,6 +596,10 @@ familiar error: You now need to create the `create` action within the `ArticlesController` for this to work. +NOTE: By default `form_with` submits forms using Ajax thereby skipping full page +redirects. To make this guide easier to get into we've disabled that with +`local: true` for now. + ### Creating articles To make the "Unknown action" go away, you can define a `create` action within @@ -909,6 +918,7 @@ And then finally, add the view for this action, located at <tr> <th>Title</th> <th>Text</th> + <th></th> </tr> <% @articles.each do |article| %> @@ -954,7 +964,7 @@ Now, add another link in `app/views/articles/new.html.erb`, underneath the form, to go back to the `index` action: ```erb -<%= form_for :article, url: articles_path do |f| %> +<%= form_with scope: :article, url: articles_path, local: true do |form| %> ... <% end %> @@ -1065,7 +1075,7 @@ something went wrong. To do that, you'll modify `app/views/articles/new.html.erb` to check for error messages: ```html+erb -<%= form_for :article, url: articles_path do |f| %> +<%= form_with scope: :article, url: articles_path, local: true do |form| %> <% if @article.errors.any? %> <div id="error_explanation"> @@ -1082,17 +1092,17 @@ something went wrong. To do that, you'll modify <% end %> <p> - <%= f.label :title %><br> - <%= f.text_field :title %> + <%= form.label :title %><br> + <%= form.text_field :title %> </p> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> @@ -1157,7 +1167,7 @@ it look as follows: ```html+erb <h1>Edit article</h1> -<%= form_for(@article) do |f| %> +<%= form_with(model: @article, local: true) do |form| %> <% if @article.errors.any? %> <div id="error_explanation"> @@ -1174,17 +1184,17 @@ it look as follows: <% end %> <p> - <%= f.label :title %><br> - <%= f.text_field :title %> + <%= form.label :title %><br> + <%= form.text_field :title %> </p> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> @@ -1195,17 +1205,16 @@ it look as follows: This time we point the form to the `update` action, which is not defined yet but will be very soon. -Passing the article object to the method, will automagically create url for submitting the edited article form. +Passing the article object to the method, will automagically create url for submitting the edited article form. This option tells Rails that we want this form to be submitted via the `PATCH` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. -The first parameter of `form_for` can be an object, say, `@article` which would +The arguments to `form_with` could be model objects, say, `model: @article` which would cause the helper to fill in the form with the fields of the object. Passing in a -symbol (`:article`) with the same name as the instance variable (`@article`) -also automagically leads to the same behavior. -More details can be found in [form_for documentation] -(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for). +symbol scope (`scope: :article`) just creates the fields but without anything filled into them. +More details can be found in [form_with documentation] +(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with). Next, we need to create the `update` action in `app/controllers/articles_controller.rb`. @@ -1302,7 +1311,7 @@ Create a new file `app/views/articles/_form.html.erb` with the following content: ```html+erb -<%= form_for @article do |f| %> +<%= form_with model: @article, local: true do |form| %> <% if @article.errors.any? %> <div id="error_explanation"> @@ -1319,29 +1328,29 @@ content: <% end %> <p> - <%= f.label :title %><br> - <%= f.text_field :title %> + <%= form.label :title %><br> + <%= form.text_field :title %> </p> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> ``` -Everything except for the `form_for` declaration remained the same. -The reason we can use this shorter, simpler `form_for` declaration +Everything except for the `form_with` declaration remained the same. +The reason we can use this shorter, simpler `form_with` declaration to stand in for either of the other forms is that `@article` is a *resource* corresponding to a full set of RESTful routes, and Rails is able to infer which URI and method to use. -For more information about this use of `form_for`, see [Resource-oriented style] -(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style). +For more information about this use of `form_with`, see [Resource-oriented style] +(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with-label-Resource-oriented+style). Now, let's update the `app/views/articles/new.html.erb` view to use this new partial, rewriting it completely: @@ -1488,14 +1497,14 @@ second argument, and then the options as another argument. The `method: :delete` and `data: { confirm: 'Are you sure?' }` options are used as HTML5 attributes so that when the link is clicked, Rails will first show a confirm dialog to the user, and then submit the link with method `delete`. This is done via the -JavaScript file `jquery_ujs` which is automatically included in your +JavaScript file `rails-ujs` which is automatically included in your application's layout (`app/views/layouts/application.html.erb`) when you generated the application. Without this file, the confirmation dialog box won't appear. ![Confirm Dialog](images/getting_started/confirm_dialog.png) -TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on +TIP: Learn more about Unobtrusive JavaScript on [Working With JavaScript in Rails](working_with_javascript_in_rails.html) guide. Congratulations, you can now create, show, list, update and destroy @@ -1544,8 +1553,8 @@ You'll learn a little about associations in the next section of this guide. The (`:references`) keyword used in the bash command is a special data type for models. It creates a new column on your database table with the provided model name appended with an `_id` -that can hold integer values. You can get a better understanding after analyzing the -`db/schema.rb` file below. +that can hold integer values. To get a better understanding, analyze the +`db/schema.rb` file after running the migration. In addition to the model, Rails has also made a migration to create the corresponding database table: @@ -1680,17 +1689,17 @@ So first, we'll wire up the Article show template </p> <h2>Add a comment:</h2> -<%= form_for([@article, @article.comments.build]) do |f| %> +<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %> <p> - <%= f.label :commenter %><br> - <%= f.text_field :commenter %> + <%= form.label :commenter %><br> + <%= form.text_field :commenter %> </p> <p> - <%= f.label :body %><br> - <%= f.text_area :body %> + <%= form.label :body %><br> + <%= form.text_area :body %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> @@ -1699,7 +1708,7 @@ So first, we'll wire up the Article show template ``` This adds a form on the `Article` show page that creates a new comment by -calling the `CommentsController` `create` action. The `form_for` call here uses +calling the `CommentsController` `create` action. The `form_with` call here uses an array, which will build a nested route, such as `/articles/1/comments`. Let's wire up the `create` in `app/controllers/comments_controller.rb`: @@ -1761,17 +1770,17 @@ add that to the `app/views/articles/show.html.erb`. <% end %> <h2>Add a comment:</h2> -<%= form_for([@article, @article.comments.build]) do |f| %> +<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %> <p> - <%= f.label :commenter %><br> - <%= f.text_field :commenter %> + <%= form.label :commenter %><br> + <%= form.text_field :commenter %> </p> <p> - <%= f.label :body %><br> - <%= f.text_area :body %> + <%= form.label :body %><br> + <%= form.text_area :body %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> @@ -1827,17 +1836,17 @@ following: <%= render @article.comments %> <h2>Add a comment:</h2> -<%= form_for([@article, @article.comments.build]) do |f| %> +<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %> <p> - <%= f.label :commenter %><br> - <%= f.text_field :commenter %> + <%= form.label :commenter %><br> + <%= form.text_field :commenter %> </p> <p> - <%= f.label :body %><br> - <%= f.text_area :body %> + <%= form.label :body %><br> + <%= form.text_area :body %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> @@ -1857,17 +1866,17 @@ Let us also move that new comment section out to its own partial. Again, you create a file `app/views/comments/_form.html.erb` containing: ```html+erb -<%= form_for([@article, @article.comments.build]) do |f| %> +<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %> <p> - <%= f.label :commenter %><br> - <%= f.text_field :commenter %> + <%= form.label :commenter %><br> + <%= form.text_field :commenter %> </p> <p> - <%= f.label :body %><br> - <%= f.text_area :body %> + <%= form.label :body %><br> + <%= form.text_area :body %> </p> <p> - <%= f.submit %> + <%= form.submit %> </p> <% end %> ``` diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 0b7cc055be..2b545e6b82 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -105,7 +105,7 @@ This means, that in the `:en` locale, the key _hello_ will map to the _Hello wor The I18n library will use **English** as a **default locale**, i.e. if a different locale is not set, `:en` will be used for looking up translations. -NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. +NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](https://groups.google.com/forum/#!topic/rails-i18n/FN7eLH2-lHA)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. The **translations load path** (`I18n.load_path`) is an array of paths to files that will be loaded automatically. Configuring this path allows for customization of translations directory structure and file naming scheme. @@ -310,10 +310,10 @@ In general, this approach is far less reliable than using the language header an #### Storing the Locale from the Session or Cookies -WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. +WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](https://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](https://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. Internationalization and Localization ------------------------------------ +------------------------------------- OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. @@ -371,7 +371,7 @@ end ```html+erb # app/views/home/index.html.erb -<h1><%=t :hello_world %></h1> +<h1><%= t :hello_world %></h1> <p><%= flash[:notice] %></p> ``` @@ -416,8 +416,8 @@ If your translations are stored in YAML files, certain keys must be escaped. The Examples: -```erb -# confing/locales/en.yml +```yaml +# config/locales/en.yml en: success: 'true': 'True!' @@ -430,12 +430,12 @@ en: ``` ```ruby -I18n.t 'success.true' # => 'True!' -I18n.t 'success.on' # => 'On!' +I18n.t 'success.true' # => 'True!' +I18n.t 'success.on' # => 'On!' I18n.t 'success.false' # => 'False!' I18n.t 'failure.false' # => Translation Missing -I18n.t 'failure.off' # => Translation Missing -I18n.t 'failure.true' # => Translation Missing +I18n.t 'failure.off' # => Translation Missing +I18n.t 'failure.true' # => Translation Missing ``` ### Passing Variables to Translations @@ -502,7 +502,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali ```erb # app/views/home/index.html.erb -<h1><%=t :hello_world %></h1> +<h1><%= t :hello_world %></h1> <p><%= flash[:notice] %></p> <p><%= l Time.now, format: :short %></p> ``` @@ -701,12 +701,15 @@ end ### Pluralization -In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar), [Japanese](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ja), [Russian](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://cldr.unicode.org/index/cldr-spec/plural-rules). Thus, the I18n API provides a flexible pluralization feature. +In many languages — including English — there are only two forms, a singular and a plural, for +a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar), [Japanese](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ja), [Russian](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://cldr.unicode.org/index/cldr-spec/plural-rules). Thus, the I18n API provides a flexible pluralization feature. -The `:count` interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR: +The `:count` interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined in the +pluralization backend. By default, only the English pluralization rules are applied. ```ruby I18n.backend.store_translations :en, inbox: { + zero: 'no messages', # optional one: 'one message', other: '%{count} messages' } @@ -715,18 +718,39 @@ I18n.translate :inbox, count: 2 I18n.translate :inbox, count: 1 # => 'one message' + +I18n.translate :inbox, count: 0 +# => 'no messages' ``` The algorithm for pluralizations in `:en` is as simple as: ```ruby -entry[count == 1 ? 0 : 1] +lookup_key = :zero if count == 0 && entry.has_key?(:zero) +lookup_key ||= count == 1 ? :one : :other +entry[lookup_key] ``` -I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero). +The translation denoted as `:one` is regarded as singular, and the `:other` is used as plural. If the count is zero, and a `:zero` entry is present, then it will be used instead of `:other`. If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised. +#### Locale-specific rules + +The I18n gem provides a Pluralization backend that can be used to enable locale-specific rules. Include it +to the Simple backend, then add the localized pluralization algorithms to translation store, as `i18n.plural.rule`. + +```ruby +I18n::Backend::Simple.include(I18n::Backend::Pluralization) +I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } } +I18n.backend.store_translations :pt, apples: { one: 'one or none', other: 'more than one' } + +I18n.t :apples, count: 0, locale: :pt +# => 'one or none' +``` + +Alternatively, the separate gem [rails-i18n](https://github.com/svenfuchs/rails-i18n) can be used to provide a fuller set of locale-specific pluralization rules. + ### Setting and Passing a Locale The locale can be either set pseudo-globally to `I18n.locale` (which uses `Thread.current` like, e.g., `Time.zone`) or can be passed as an option to `#translate` and `#localize`. @@ -953,7 +977,7 @@ en: ``` NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form) -gem by adding this line to your Gemfile: `gem 'dynamic_form'`. +gem by adding this line to your `Gemfile`: `gem 'dynamic_form'`. ### Translations for Action Mailer E-Mail Subjects @@ -1026,7 +1050,7 @@ The Simple backend shipped with Active Support allows you to store translations For example a Ruby Hash providing translations can look like this: -```yaml +```ruby { pt: { foo: { @@ -1147,7 +1171,7 @@ Conclusion At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. -If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](http://groups.google.com/group/rails-i18n). +If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](https://groups.google.com/forum/#!forum/rails-i18n). Contributing to Rails I18n @@ -1155,7 +1179,7 @@ Contributing to Rails I18n I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in gems and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. -Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n)!) +Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](https://groups.google.com/forum/#!forum/rails-i18n)!) If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://help.github.com/articles/about-pull-requests/). @@ -1163,7 +1187,7 @@ If you find your own locale (language) missing from our [example translations da Resources --------- -* [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. +* [Google group: rails-i18n](https://groups.google.com/forum/#!forum/rails-i18n) - The project's mailing list. * [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n) - Code repository and issue tracker for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. * [GitHub: i18n](https://github.com/svenfuchs/i18n) - Code repository and issue tracker for the i18n gem. @@ -1177,7 +1201,7 @@ Authors Footnotes --------- -[^1]: Or, to quote [Wikipedia](http://en.wikipedia.org/wiki/Internationalization_and_localization): _"Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."_ +[^1]: Or, to quote [Wikipedia](https://en.wikipedia.org/wiki/Internationalization_and_localization): _"Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."_ [^2]: Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files. diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 3ea156c6fe..c4f1df487b 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -93,29 +93,29 @@ require 'bundler/setup' # Set up gems listed in the Gemfile. In a standard Rails application, there's a `Gemfile` which declares all dependencies of the application. `config/boot.rb` sets -`ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile +`ENV['BUNDLE_GEMFILE']` to the location of this file. If the `Gemfile` exists, then `bundler/setup` is required. The require is used by Bundler to configure the load path for your Gemfile's dependencies. A standard Rails application depends on several gems, specifically: +* actioncable * actionmailer * actionpack * actionview +* activejob * activemodel * activerecord +* activestorage * activesupport -* activejob * arel * builder * bundler -* erubis +* erubi * i18n * mail * mime-types * rack -* rack-cache -* rack-mount * rack-test * rails * railties @@ -131,7 +131,7 @@ Once `config/boot.rb` has finished, the next file that is required is `ARGV` array simply contains `server` which will be passed over: ```ruby -require "rails/command" +require_relative "command" aliases = { "g" => "generate", @@ -155,7 +155,7 @@ defined here to find the matching command. ### `rails/command.rb` When one types a Rails command, `invoke` tries to lookup a command for the given -namespace and executing the command if found. +namespace and executes the command if found. If Rails doesn't recognize the command, it hands the reins over to Rake to run a task of the same name. @@ -170,7 +170,7 @@ module Rails::Command namespace = namespace.to_s namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace) namespace = "version" if %w( -v --version ).include? namespace - + if command = find_by_namespace(namespace) command.perform(namespace, args, config) else @@ -189,7 +189,7 @@ module Rails class ServerCommand < Base # :nodoc: def perform set_application_directory! - + Rails::Server.new.tap do |server| # Require application after server sets environment to propagate # the --environment option. @@ -311,7 +311,7 @@ def parse!(args) args, options = args.dup, {} option_parser(options).parse! args - + options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development" options[:server] = args.shift options @@ -366,11 +366,11 @@ private def log_to_stdout wrapped_app # touch the app so the logger is set up - + console = ActiveSupport::Logger.new(STDOUT) console.formatter = Rails.logger.formatter console.level = Rails.logger.level - + unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT) Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) end @@ -537,6 +537,7 @@ require "rails" action_mailer/railtie active_job/railtie action_cable/engine + active_storage/engine rails/test_unit/railtie sprockets/railtie ).each do |railtie| diff --git a/guides/source/kindle/rails_guides.opf.erb b/guides/source/kindle/rails_guides.opf.erb index 547abcbc19..63eeb007d7 100644 --- a/guides/source/kindle/rails_guides.opf.erb +++ b/guides/source/kindle/rails_guides.opf.erb @@ -5,7 +5,7 @@ <meta name="cover" content="cover" /> <dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> - <dc:title>Ruby on Rails Guides (<%= @version %>)</dc:title> + <dc:title>Ruby on Rails Guides (<%= @version || "master@#{@edge[0, 7]}" %>)</dc:title> <dc:language>en-us</dc:language> <dc:creator>Ruby on Rails</dc:creator> diff --git a/guides/source/kindle/toc.html.erb b/guides/source/kindle/toc.html.erb index f310edd3a1..0f4228ed6b 100644 --- a/guides/source/kindle/toc.html.erb +++ b/guides/source/kindle/toc.html.erb @@ -14,7 +14,7 @@ Ruby on Rails Guides <% if document['work_in_progress']%>(WIP)<% end %> </li> <% end %> - </ul> + </ul> <% end %> <hr /> <ul> diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index bb50761b30..3981199e95 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -32,7 +32,7 @@ <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li> <li class="more-info"><a href="http://guides.rubyonrails.org/">Guides</a></li> <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li> - <li class="more-info"><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li> + <li class="more-info"><a href="https://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li> <li class="more-info"><a href="https://github.com/rails/rails">Contribute on GitHub</a></li> </ul> </div> @@ -99,9 +99,9 @@ To get started, you can read our <%= link_to 'documentation contributions', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section. </p> <p> - You may also find incomplete content, or stuff that is not up to date. + You may also find incomplete content or stuff that is not up to date. Please do add any missing documentation for master. Make sure to check - <%= link_to 'Edge Guides','http://edgeguides.rubyonrails.org' %> first to verify + <%= link_to 'Edge Guides', 'http://edgeguides.rubyonrails.org' %> first to verify if the issues are already fixed or not on the master branch. Check the <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %> for style and conventions. @@ -111,7 +111,7 @@ <%= link_to 'open an issue', 'https://github.com/rails/rails/issues' %>. </p> <p>And last but not least, any kind of discussion regarding Ruby on Rails - documentation is very welcome in the <%= link_to 'rubyonrails-docs mailing list', 'https://groups.google.com/forum/#!forum/rubyonrails-docs' %>. + documentation is very welcome on the <%= link_to 'rubyonrails-docs mailing list', 'https://groups.google.com/forum/#!forum/rubyonrails-docs' %>. </p> </div> </div> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 293c99fc8f..4d79b2db89 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -71,23 +71,25 @@ If we want to display the properties of all the books in our view, we can do so <h1>Listing Books</h1> <table> - <tr> - <th>Title</th> - <th>Summary</th> - <th></th> - <th></th> - <th></th> - </tr> - -<% @books.each do |book| %> - <tr> - <td><%= book.title %></td> - <td><%= book.content %></td> - <td><%= link_to "Show", book %></td> - <td><%= link_to "Edit", edit_book_path(book) %></td> - <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td> - </tr> -<% end %> + <thead> + <tr> + <th>Title</th> + <th>Content</th> + <th colspan="3"></th> + </tr> + </thead> + + <tbody> + <% @books.each do |book| %> + <tr> + <td><%= book.title %></td> + <td><%= book.content %></td> + <td><%= link_to "Show", book %></td> + <td><%= link_to "Edit", edit_book_path(book) %></td> + <td><%= link_to "Destroy", book, method: :delete, data: { confirm: "Are you sure?" } %></td> + </tr> + <% end %> + </tbody> </table> <br> @@ -221,7 +223,7 @@ service requests that are expecting something other than proper HTML. NOTE: By default, if you use the `:plain` option, the text is rendered without using the current layout. If you want Rails to put the text into the current -layout, you need to add the `layout: true` option and use the `.txt.erb` +layout, you need to add the `layout: true` option and use the `.text.erb` extension for the layout file. #### Rendering HTML @@ -230,14 +232,14 @@ You can send an HTML string back to the browser by using the `:html` option to `render`: ```ruby -render html: "<strong>Not Found</strong>".html_safe +render html: helpers.tag.strong('Not Found') ``` TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method. +NOTE: When using `html:` option, HTML entities will be escaped if the string is not composed with `html_safe`-aware APIs. #### Rendering JSON @@ -283,7 +285,7 @@ the response. Using `:plain` or `:html` might be more appropriate most of the time. NOTE: Unless overridden, your response returned from this render option will be -`text/html`, as that is the default content type of Action Dispatch response. +`text/plain`, as that is the default content type of Action Dispatch response. #### Options for `render` @@ -379,6 +381,7 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 415 | :unsupported_media_type | | | 416 | :range_not_satisfiable | | | 417 | :expectation_failed | +| | 421 | :misdirected_request | | | 422 | :unprocessable_entity | | | 423 | :locked | | | 424 | :failed_dependency | @@ -386,6 +389,7 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 428 | :precondition_required | | | 429 | :too_many_requests | | | 431 | :request_header_fields_too_large | +| | 451 | :unavailable_for_legal_reasons | | **Server Error** | 500 | :internal_server_error | | | 501 | :not_implemented | | | 502 | :bad_gateway | @@ -411,6 +415,8 @@ render formats: :xml render formats: [:json, :xml] ``` +If a template with the specified format does not exist an `ActionView::MissingTemplate` error is raised. + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. @@ -766,7 +772,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th #### Linking to Feeds with the `auto_discovery_link_tag` -The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag: +The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS, Atom, or JSON feeds. It takes the type of the link (`:rss`, `:atom`, or `:json`), a hash of options that are passed through to url_for, and a hash of options for the tag: ```erb <%= auto_discovery_link_tag(:rss, {action: "feed"}, @@ -1169,7 +1175,7 @@ To pass a local variable to a partial in only specific cases use the `local_assi This way it is possible to use the partial without the need to declare all local variables. -Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the `:object` option: +Every partial also has a local variable with the same name as the partial (minus the leading underscore). You can pass an object in to this local variable via the `:object` option: ```erb <%= render partial: "customer", object: @new_customer %> @@ -1260,7 +1266,7 @@ You can also pass in arbitrary local variables to any partial you are rendering In this case, the partial will have access to a local variable `title` with the value "Products Page". -TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option. +TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view. You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option: diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index 7ced3eab1c..1d6a4edb5b 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -44,7 +44,7 @@ from. In special situations, where someone from the Core Team agrees to support more series, they are included in the list of supported series. -**Currently included series:** `5.0.Z`, `4.2.Z`. +**Currently included series:** `5.1.Z`. Security Issues --------------- @@ -59,7 +59,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that security releases are easy to upgrade to if you're running the latest version of Rails. -**Currently included series:** `5.0.Z`, `4.2.Z`. +**Currently included series:** `5.1.Z`, `5.0.Z`. Severe Security Issues ---------------------- @@ -68,7 +68,7 @@ For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. -**Currently included series:** `5.0.Z`, `4.2.Z`. +**Currently included series:** `5.1.Z`, `5.0.Z`, `4.2.Z`. Unsupported Release Series -------------------------- diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md deleted file mode 100644 index 71efa4b0d0..0000000000 --- a/guides/source/nested_model_forms.md +++ /dev/null @@ -1,230 +0,0 @@ -**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** - -Rails Nested Model Forms -======================== - -Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations. - -After reading this guide, you will know: - -* do stuff. - --------------------------------------------------------------------------------- - -NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it's **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/). - - -Model setup ------------ - -To be able to use the nested model functionality in your forms, the model will need to support some basic operations. - -First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built. - -If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded. - -Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`. - -### ActiveRecord::Base model - -For an ActiveRecord::Base model and association this writer method is commonly defined with the `accepts_nested_attributes_for` class method: - -#### has_one - -```ruby -class Person < ApplicationRecord - has_one :address - accepts_nested_attributes_for :address -end -``` - -#### belongs_to - -```ruby -class Person < ApplicationRecord - belongs_to :firm - accepts_nested_attributes_for :firm -end -``` - -#### has_many / has_and_belongs_to_many - -```ruby -class Person < ApplicationRecord - has_many :projects - accepts_nested_attributes_for :projects -end -``` - -NOTE: For greater detail on associations see [Active Record Associations](association_basics.html). -For a complete reference on associations please visit the API documentation for [ActiveRecord::Associations::ClassMethods](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html). - -### Custom model - -As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: - -#### Single associated object - -```ruby -class Person - def address - Address.new - end - - def address_attributes=(attributes) - # ... - end -end -``` - -#### Association collection - -```ruby -class Person - def projects - [Project.new, Project.new] - end - - def projects_attributes=(attributes) - # ... - end -end -``` - -NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model. - -Views ------ - -### Controller code - -A nested model form will _only_ be built if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. - -Consider the following typical RESTful controller which will prepare a new Person instance and its `address` and `projects` associations before rendering the `new` template: - -```ruby -class PeopleController < ApplicationController - def new - @person = Person.new - @person.build_address - 2.times { @person.projects.build } - end - - def create - @person = Person.new(params[:person]) - if @person.save - # ... - end - end -end -``` - -NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an `after_initialize` callback which is a good way to refactor this. - -### Form code - -Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form. - -#### Standard form - -Start out with a regular RESTful form: - -```erb -<%= form_for @person do |f| %> - <%= f.text_field :name %> -<% end %> -``` - -This will generate the following html: - -```html -<form action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> -</form> -``` - -#### Nested form for a single associated object - -Now add a nested form for the `address` association: - -```erb -<%= form_for @person do |f| %> - <%= f.text_field :name %> - - <%= f.fields_for :address do |af| %> - <%= af.text_field :street %> - <% end %> -<% end %> -``` - -This generates: - -```html -<form action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> - - <input id="person_address_attributes_street" name="person[address_attributes][street]" type="text" /> -</form> -``` - -Notice that `fields_for` recognized the `address` as an association for which a nested model form should be built by the way it has namespaced the `name` attribute. - -When this form is posted the Rails parameter parser will construct a hash like the following: - -```ruby -{ - "person" => { - "name" => "Eloy Duran", - "address_attributes" => { - "street" => "Nieuwe Prinsengracht" - } - } -} -``` - -That's it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved. - -#### Nested form for a collection of associated objects - -The form code for an association collection is pretty similar to that of a single associated object: - -```erb -<%= form_for @person do |f| %> - <%= f.text_field :name %> - - <%= f.fields_for :projects do |pf| %> - <%= pf.text_field :name %> - <% end %> -<% end %> -``` - -Which generates: - -```html -<form action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> - - <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" type="text" /> - <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" type="text" /> -</form> -``` - -As you can see it has generated 2 `project name` inputs, one for each new `project` that was built in the controller's `new` action. Only this time the `name` attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: - -```ruby -{ - "person" => { - "name" => "Eloy Duran", - "projects_attributes" => { - "0" => { "name" => "Project 1" }, - "1" => { "name" => "Project 2" } - } - } -} -``` - -You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance. - -NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep. - -TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example. diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 760ff431c0..15073af6be 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -74,7 +74,7 @@ In this example you will add a method to String named `to_squawk`. To begin, cre ```ruby # yaffle/test/core_ext_test.rb -require 'test_helper' +require "test_helper" class CoreExtTest < ActiveSupport::TestCase def test_to_squawk_prepends_the_word_squawk @@ -104,14 +104,16 @@ Finished in 0.003358s, 595.6483 runs/s, 297.8242 assertions/s. Great - now you are ready to start development. -In `lib/yaffle.rb`, add `require 'yaffle/core_ext'`: +In `lib/yaffle.rb`, add `require "yaffle/core_ext"`: ```ruby # yaffle/lib/yaffle.rb -require 'yaffle/core_ext' +require "yaffle/railtie" +require "yaffle/core_ext" module Yaffle + # Your code goes here... end ``` @@ -120,7 +122,7 @@ Finally, create the `core_ext.rb` file and add the `to_squawk` method: ```ruby # yaffle/lib/yaffle/core_ext.rb -String.class_eval do +class String def to_squawk "squawk! #{self}".strip end @@ -133,7 +135,7 @@ To test that your method does what it says it does, run the unit tests with `bin 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips ``` -To see this in action, change to the test/dummy directory, fire up a console and start squawking: +To see this in action, change to the `test/dummy` directory, fire up a console and start squawking: ```bash $ bin/rails console @@ -152,7 +154,7 @@ To begin, set up your files so that you have: ```ruby # yaffle/test/acts_as_yaffle_test.rb -require 'test_helper' +require "test_helper" class ActsAsYaffleTest < ActiveSupport::TestCase end @@ -161,10 +163,12 @@ end ```ruby # yaffle/lib/yaffle.rb -require 'yaffle/core_ext' -require 'yaffle/acts_as_yaffle' +require "yaffle/railtie" +require "yaffle/core_ext" +require "yaffle/acts_as_yaffle" module Yaffle + # Your code goes here... end ``` @@ -173,7 +177,6 @@ end module Yaffle module ActsAsYaffle - # your code will go here end end ``` @@ -189,7 +192,7 @@ To start out, write a failing test that shows the behavior you'd like: ```ruby # yaffle/test/acts_as_yaffle_test.rb -require 'test_helper' +require "test_helper" class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_hickwalls_yaffle_text_field_should_be_last_squawk @@ -234,7 +237,7 @@ Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s. This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test. We can easily generate these models in our "dummy" Rails application by running the following commands from the -test/dummy directory: +`test/dummy` directory: ```bash $ cd test/dummy @@ -276,12 +279,8 @@ module Yaffle module ActsAsYaffle extend ActiveSupport::Concern - included do - end - - module ClassMethods + class_methods do def acts_as_yaffle(options = {}) - # your code will go here end end end @@ -335,13 +334,9 @@ module Yaffle module ActsAsYaffle extend ActiveSupport::Concern - included do - end - - module ClassMethods + class_methods do def acts_as_yaffle(options = {}) - cattr_accessor :yaffle_text_field - self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s + cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s end end end @@ -364,14 +359,14 @@ When you run `bin/test`, you should see the tests all pass: ### Add an Instance Method -This plugin will add a method named 'squawk' to any Active Record object that calls 'acts_as_yaffle'. The 'squawk' +This plugin will add a method named 'squawk' to any Active Record object that calls `acts_as_yaffle`. The 'squawk' method will simply set the value of one of the fields in the database. To start out, write a failing test that shows the behavior you'd like: ```ruby # yaffle/test/acts_as_yaffle_test.rb -require 'test_helper' +require "test_helper" class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_hickwalls_yaffle_text_field_should_be_last_squawk @@ -397,7 +392,7 @@ end ``` Run the test to make sure the last two tests fail with an error that contains "NoMethodError: undefined method `squawk'", -then update 'acts_as_yaffle.rb' to look like this: +then update `acts_as_yaffle.rb` to look like this: ```ruby # yaffle/lib/yaffle/acts_as_yaffle.rb @@ -407,20 +402,14 @@ module Yaffle extend ActiveSupport::Concern included do - end - - module ClassMethods - def acts_as_yaffle(options = {}) - cattr_accessor :yaffle_text_field - self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s - - include Yaffle::ActsAsYaffle::LocalInstanceMethods + def squawk(string) + write_attribute(self.class.yaffle_text_field, string.to_squawk) end end - module LocalInstanceMethods - def squawk(string) - write_attribute(self.class.yaffle_text_field, string.to_squawk) + class_methods do + def acts_as_yaffle(options = {}) + cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s end end end @@ -450,23 +439,23 @@ send("#{self.class.yaffle_text_field}=", string.to_squawk) Generators ---------- -Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about -the creation of generators can be found in the [Generators Guide](generators.html) +Generators can be included in your gem simply by creating them in a `lib/generators` directory of your plugin. More information about +the creation of generators can be found in the [Generators Guide](generators.html). Publishing Your Gem ------------------- Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply -commit the code to a Git repository (like GitHub) and add a line to the Gemfile of the application in question: +commit the code to a Git repository (like GitHub) and add a line to the `Gemfile` of the application in question: ```ruby -gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git' +gem "yaffle", git: "https://github.com/rails/yaffle.git" ``` After running `bundle install`, your gem functionality will be available to the application. -When the gem is ready to be shared as a formal release, it can be published to [RubyGems](http://www.rubygems.org). -For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html). +When the gem is ready to be shared as a formal release, it can be published to [RubyGems](https://rubygems.org). +For more information about publishing gems to RubyGems, see: [Publishing your gem](http://guides.rubygems.org/publishing). RDoc Documentation ------------------ @@ -480,7 +469,7 @@ The first step is to update the README file with detailed information about how * How to add the functionality to the app (several examples of common use cases) * Warnings, gotchas or tips that might help users and save them time -Once your README is solid, go through and add rdoc comments to all of the methods that developers will use. It's also customary to add '#:nodoc:' comments to those parts of the code that are not included in the public API. +Once your README is solid, go through and add rdoc comments to all of the methods that developers will use. It's also customary to add `#:nodoc:` comments to those parts of the code that are not included in the public API. Once your comments are good to go, navigate to your plugin directory and run: diff --git a/guides/source/profiling.md b/guides/source/profiling.md deleted file mode 100644 index ce093f78ba..0000000000 --- a/guides/source/profiling.md +++ /dev/null @@ -1,16 +0,0 @@ -*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** - -A Guide to Profiling Rails Applications -======================================= - -This guide covers built-in mechanisms in Rails for profiling your application. - -After reading this guide, you will know: - -* Rails profiling terminology. -* How to write benchmark tests for your application. -* Other benchmarking approaches and plugins. - --------------------------------------------------------------------------------- - - diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 3e99ee7021..e087834a2f 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -277,6 +277,6 @@ relative paths to your template's location. ```ruby def source_paths - [File.expand_path(File.dirname(__FILE__))] + [__dir__] end ``` diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 340933c7ee..5718b9ddfc 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -20,9 +20,9 @@ Introduction to Rack Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. -* [Rack API Documentation](http://rack.github.io/) - -Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the [Resources](#resources) section below. +Explaining how Rack works is not really in the scope of this guide. In case you +are not familiar with Rack's basics, you should check out the [Resources](#resources) +section below. Rails on Rack ------------- @@ -74,7 +74,7 @@ And start the server: $ rackup config.ru ``` -To find out more about different `rackup` options: +To find out more about different `rackup` options, you can run: ```bash $ rackup --help @@ -89,7 +89,8 @@ Action Dispatcher Middleware Stack Many of Action Dispatcher's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application. -NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements. +NOTE: `ActionDispatch::MiddlewareStack` is Rails' equivalent of `Rack::Builder`, +but is built for better flexibility and more features to meet Rails' requirements. ### Inspecting Middleware Stack @@ -109,21 +110,23 @@ use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId +use ActionDispatch::RemoteIp +use Sprockets::Rails::QuietAssets use Rails::Rack::Logger use ActionDispatch::ShowExceptions use WebConsole::Middleware use ActionDispatch::DebugExceptions -use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash +use ActionDispatch::ContentSecurityPolicy::Middleware use Rack::Head use Rack::ConditionalGet use Rack::ETag -run Rails.application.routes +run MyApp::Application.routes ``` The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below. @@ -237,9 +240,17 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#request_id` method. +**`ActionDispatch::RemoteIp`** + +* Checks for IP spoofing attacks. + +**`Sprockets::Rails::QuietAssets`** + +* Suppresses logger output for asset requests. + **`Rails::Rack::Logger`** -* Notifies the logs that the request has began. After request is complete, flushes all the logs. +* Notifies the logs that the request has begun. After the request is complete, flushes all the logs. **`ActionDispatch::ShowExceptions`** @@ -249,10 +260,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Responsible for logging exceptions and showing a debugging page in case the request is local. -**`ActionDispatch::RemoteIp`** - -* Checks for IP spoofing attacks. - **`ActionDispatch::Reloader`** * Provides prepare and cleanup callbacks, intended to assist with code reloading during development. @@ -283,7 +290,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`Rack::ConditionalGet`** -* Adds support for "Conditional `GET`" so that server responds with nothing if page wasn't changed. +* Adds support for "Conditional `GET`" so that server responds with nothing if the page wasn't changed. **`Rack::ETag`** @@ -296,7 +303,7 @@ Resources ### Learning Rack -* [Official Rack Website](http://rack.github.io) +* [Official Rack Website](https://rack.github.io) * [Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html) ### Understanding Middlewares diff --git a/guides/source/routing.md b/guides/source/routing.md index 86492a9332..efc0e32b56 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -47,7 +47,7 @@ get '/patients/:id', to: 'patients#show', as: 'patient' and your application contains this code in the controller: ```ruby -@patient = Patient.find(17) +@patient = Patient.find(params[:id]) ``` and this in the corresponding view: @@ -142,16 +142,17 @@ Sometimes, you have a resource that clients always look up without referencing a get 'profile', to: 'users#show' ``` -Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action but you must also specify the `controller:` to use: +Passing a `String` to `to:` will expect a `controller#action` format. When using a `Symbol`, the `to:` option should be replaced with `action:`. When using a `String` without a `#`, the `to:` option should be replaced with `controller:`: ```ruby -get 'profile', to: :show, controller: 'users' +get 'profile', action: :show, controller: 'users' ``` This resourceful route: ```ruby resource :geocoder +resolve('Geocoder') { [:geocoder] } ``` creates six different routes in your application, all mapping to the `Geocoders` controller: @@ -175,14 +176,6 @@ A singular resourceful route generates these helpers: As with plural resources, the same helpers ending in `_url` will also include the host, port and path prefix. -WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) prevents `form_for` from working automatically with singular resources. As a workaround, specify the URL for the form directly, like so: - -```ruby -form_for @geocoder, url: geocoder_path do |f| - -# snippet for brevity -``` - ### Controller Namespaces and Routing You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace. You would place these controllers under the `app/controllers/admin` directory, and you can group them together in your router: @@ -425,7 +418,7 @@ resources :articles do end ``` -Also you can use them in any place that you want inside the routes, for example in a scope or namespace call: +Also you can use them in any place that you want inside the routes, for example in a `scope` or `namespace` call: ```ruby namespace :articles do @@ -491,7 +484,7 @@ resources :photos do end ``` -This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `preview_photo_url` and `preview_photo_path` helpers. +This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `photo_preview_url` and `photo_preview_path` helpers. Within the block of member routes, each route name specifies the HTTP verb will be recognized. You can use `get`, `patch`, `put`, `post`, or `delete` here @@ -545,7 +538,7 @@ TIP: If you find yourself adding many extra actions to a resourceful route, it's Non-Resourceful Routes ---------------------- -In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route within your application separately. +In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route separately within your application. While you should usually use resourceful routing, there are still many places where the simpler routing is more appropriate. There's no need to try to shoehorn every last piece of your application into a resourceful framework if that's not a good fit. @@ -647,7 +640,7 @@ match 'photos', to: 'photos#show', via: :all NOTE: Routing both `GET` and `POST` requests to a single action has security implications. In general, you should avoid routing all verbs to an action unless you have a good reason to. -NOTE: 'GET' in Rails won't check for CSRF token. You should never write to the database from 'GET' requests, for more information see the [security guide](security.html#csrf-countermeasures) on CSRF countermeasures. +NOTE: `GET` in Rails won't check for CSRF token. You should never write to the database from `GET` requests, for more information see the [security guide](security.html#csrf-countermeasures) on CSRF countermeasures. ### Segment Constraints @@ -815,14 +808,14 @@ NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesContr If you specify a Rack application as the endpoint for a matcher, remember that the route will be unchanged in the receiving application. With the following -route your Rack application should expect the route to be '/admin': +route your Rack application should expect the route to be `/admin`: ```ruby match '/admin', to: AdminApp, via: :all ``` If you would prefer to have your Rack application receive requests at the root -path instead, use mount: +path instead, use `mount`: ```ruby mount AdminApp, at: '/admin' @@ -859,6 +852,49 @@ You can specify unicode character routes directly. For example: get 'こんにちは', to: 'welcome#index' ``` +### Direct routes + +You can create custom URL helpers directly. For example: + +```ruby +direct :homepage do + "http://www.rubyonrails.org" +end + +# >> homepage_url +# => "http://www.rubyonrails.org" +``` + +The return value of the block must be a valid argument for the `url_for` method. So, you can pass a valid string URL, Hash, Array, an Active Model instance, or an Active Model class. + +```ruby +direct :commentable do |model| + [ model, anchor: model.dom_id ] +end + +direct :main do + { controller: 'pages', action: 'index', subdomain: 'www' } +end +``` + +### Using `resolve` + +The `resolve` method allows customizing polymorphic mapping of models. For example: + +``` ruby +resource :basket + +resolve("Basket") { [:basket] } +``` + +``` erb +<%= form_for @basket do |form| %> + <!-- basket form --> +<% end %> +``` + +This will generate the singular URL `/basket` instead of the usual `/baskets/:id`. + Customizing Resourceful Routes ------------------------------ diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 50866350f8..de63e193f4 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -50,6 +50,48 @@ Use the same inline formatting as regular text: ##### The `:content_type` Option ``` +Linking to the API +------------------ + +Links to the API (`api.rubyonrails.org`) are processed by the guides generator in the following manner: + +Links that include a release tag are left untouched. For example + +``` +http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html +``` + +is not modified. + +Please use these in release notes, since they should point to the corresponding version no matter the target being generated. + +If the link does not include a release tag and edge guides are being generated, the domain is replaced by `edgeapi.rubyonrails.org`. For example, + +``` +http://api.rubyonrails.org/classes/ActionDispatch/Response.html +``` + +becomes + +``` +http://edgeapi.rubyonrails.org/classes/ActionDispatch/Response.html +``` + +If the link does not include a release tag and release guides are being generated, the Rails version is injected. For example, if we are generating the guides for v5.1.0 the link + +``` +http://api.rubyonrails.org/classes/ActionDispatch/Response.html +``` + +becomes + +``` +http://api.rubyonrails.org/v5.1.0/classes/ActionDispatch/Response.html +``` + +Please don't link to `edgeapi.rubyonrails.org` manually. + + API Documentation Guidelines ---------------------------- @@ -97,8 +139,6 @@ By default, guides that have not been modified are not processed, so `ONLY` is r To force processing all the guides, pass `ALL=1`. -It is also recommended that you work with `WARNINGS=1`. This detects duplicate IDs and warns about broken internal links. - If you want to generate guides in a language other than English, you can keep them in a separate directory under `source` (eg. `source/es`) and use the `GUIDES_LANGUAGE` environment variable: ``` diff --git a/guides/source/security.md b/guides/source/security.md index a81a782cf2..916b1e32f8 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,7 +1,7 @@ **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** -Ruby on Rails Security Guide -============================ +Securing Rails Applications +=========================== This manual describes common security problems in web applications and how to avoid them with Rails. @@ -52,7 +52,7 @@ User.find(session[:user_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) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs. +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. ### Session Hijacking @@ -85,39 +85,117 @@ This will also be a good idea, if you modify the structure of an object and old * _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. -### Session Storage +### Encrypted Session Storage NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._ -Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly 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 of it: +The `CookieStore` saves the session hash directly 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. + +* 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. + +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 +data. Cookie-based sessions thus provide both integrity as well as +confidentiality to their contents. The encryption key, as well as the +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!__ + +It is also important to use different salt values for encrypted and +signed cookies. Using the same value for different salt configuration +values may lead to the same derived key being used for different +security features which in turn may weaken the strength of the key. + +In test and development applications get a `secret_key_base` derived from the app name. Other environments must use a random key present in `config/credentials.yml.enc`, shown here in its decrypted state: + + secret_key_base: 492f... -* 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 usually ok_. +If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret. -* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie. +### Rotating Encrypted and Signed Cookies Configurations -However, since Rails 4, the default store is EncryptedCookieStore. With -EncryptedCookieStore the session is encrypted before being stored in a cookie. -This prevents the user from accessing and tampering the content of the cookie. -Thus the session becomes a more secure place to store data. The encryption is -done using a server-side secret key `secrets.secret_key_base` stored in -`config/secrets.yml`. +Rotation is ideal for changing cookie configurations and ensuring old cookies +aren't immediately invalid. Your users then have a chance to visit your site, +get their cookie read with an old configuration and have it rewritten with the +new change. The rotation can then be removed once you're comfortable enough +users have had their chance to get their cookies upgraded. -That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_. +It's possible to rotate the ciphers and digests used for encrypted and signed cookies. -`secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.: +For instance to change the digest used for signed cookies from SHA1 to SHA256, +you would first assign the new configuration value: - development: - secret_key_base: a75d... +```ruby +Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256" +``` - test: - secret_key_base: 492f... +Now add a rotation for the old SHA1 digest so existing cookies are +seamlessly upgraded to the new SHA256 digest. - production: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> +```ruby +Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies| + cookies.rotate :signed, digest: "SHA1" +end +``` -Older versions of Rails use CookieStore, which uses `secret_token` instead of `secret_key_base` that is used by EncryptedCookieStore. Read the upgrade documentation for more information. +Then any written signed cookies will be digested with SHA256. Old cookies +that were written with SHA1 can still be read, and if accessed will be written +with the new digest so they're upgraded and won't be invalid when you remove the +rotation. -If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret. +Once users with SHA1 digested signed cookies should no longer have a chance to +have their cookies rewritten, remove the rotation. + +While you can setup as many rotations as you'd like it's not common to have many +rotations going at any one time. + +For more details on key rotation with encrypted and signed messages as +well as the various options the `rotate` method accepts, please refer to +the +[MessageEncryptor API](http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html) +and +[MessageVerifier API](http://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html) +documentation. ### Replay Attacks for CookieStore Sessions @@ -182,7 +260,7 @@ class Session < ApplicationRecord end ``` -The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a created_at column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above: +The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a `created_at` column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above: ```ruby delete_all "updated_at < '#{time.ago.to_s(:db)}' OR @@ -212,7 +290,7 @@ CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less th NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._ -The HTTP protocol basically provides two main types of requests - GET and POST (and more, but they are not supported by most browsers). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST: +The HTTP protocol basically provides two main types of requests - GET and POST (DELETE, PUT, and PATCH should be used like POST). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST: **Use GET if:** @@ -224,7 +302,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST ( * The interaction _changes the state_ of the resource in a way that the user would perceive (e.g., a subscription to a service), or * The user is _held accountable for the results_ of the interaction. -If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however, do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier. +If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Some legacy web browsers, however, do not support them - only GET and POST. Rails uses a hidden `_method` field to handle these cases. _POST requests can be sent automatically, too_. In this example, the link www.harmless.com is shown as the destination in the browser's status bar. But it has actually dynamically created a new form that sends a POST request. @@ -257,13 +335,12 @@ protect_from_forgery with: :exception This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown. -NOTE: By default, Rails includes jQuery and an [unobtrusive scripting adapter for -jQuery](https://github.com/rails/jquery-ujs), which adds a header called -`X-CSRF-Token` on every non-GET Ajax call made by jQuery with the security token. -Without this header, non-GET Ajax requests won't be accepted by Rails. When using -another library to make Ajax calls, it is necessary to add the security token as -a default header for Ajax calls in your library. To get the token, have a look at -`<meta name='csrf-token' content='THE-TOKEN'>` tag printed by +NOTE: By default, Rails includes an [unobtrusive scripting adapter](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts), +which adds a header called `X-CSRF-Token` with the security token on every non-GET +Ajax call. Without this header, non-GET Ajax requests won't be accepted by Rails. +When using another library to make Ajax calls, it is necessary to add the security +token as a default header for Ajax calls in your library. To get the token, have +a look at `<meta name='csrf-token' content='THE-TOKEN'>` tag printed by `<%= csrf_meta_tags %>` in your application view. It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself: @@ -357,7 +434,7 @@ send_file('/var/www/uploads/' + params[:filename]) Simply pass a file name like "../../../etc/passwd" to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_: ```ruby -basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files')) +basename = File.expand_path('../../files', __dir__) filename = File.expand_path(File.join(basename, @file.public_filename)) raise if basename != File.expand_path(File.join(File.dirname(filename), '../../../')) @@ -459,7 +536,7 @@ Depending on your web application, there may be more ways to hijack the user's a INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect registration forms from attackers and comment forms from automatic spam bots by asking the user to type the letters of a distorted image. This is the positive CAPTCHA, but there is also the negative CAPTCHA. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._ -A popular positive CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API. +A popular positive CAPTCHA API is [reCAPTCHA](https://developers.google.com/recaptcha/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API. You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails. The problem with CAPTCHAs is that they have a negative impact on the user experience. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. Still, positive CAPTCHAs are one of the best methods to prevent all kinds of bots from submitting forms. @@ -615,7 +692,7 @@ The two dashes start a comment ignoring everything after it. So the query return Usually a web application includes access control. The user enters their login credentials and the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. ```ruby -User.first("login = '#{params[:name]}' AND password = '#{params[:password]}'") +User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'") ``` If an attacker enters ' OR '1'='1 as the name, and ' OR '2'>'1 as the password, the resulting SQL query will be: @@ -681,7 +758,7 @@ The most common entry points are message posts, user comments, and guest books, XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser. -During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The [Symantec Global Internet Security threat report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf) also documented 239 browser plug-in vulnerabilities in the last six months of 2007. [Mpack](http://pandalabs.pandasecurity.com/mpack-uncovered/) is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets. +During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The [Symantec Global Internet Security threat report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf) also documented 239 browser plug-in vulnerabilities in the last six months of 2007. [Mpack](http://pandalabs.pandasecurity.com/mpack-uncovered/) is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high profile targets. #### HTML/JavaScript Injection @@ -762,7 +839,7 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title)) This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags. -As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &, ", <, and > by their uninterpreted representations in HTML (`&`, `"`, `<`, and `>`). +As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &, ", <, and > by their uninterpreted representations in HTML (`&`, `"`, `<`, and `>`). ##### Obfuscation and Encoding Injection @@ -797,7 +874,7 @@ In December 2006, 34,000 actual user names and passwords were stolen in a [MySpa INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._ -CSS Injection is explained best by the well-known [MySpace Samy worm](http://namb.la/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm. +CSS Injection is explained best by the well-known [MySpace Samy worm](https://samy.pl/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm. MySpace blocked many tags, but allowed CSS. So the worm's author put JavaScript into CSS like this: @@ -1019,34 +1096,40 @@ Here is a list of common headers: * **X-Content-Type-Options:** _'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file. * **X-Content-Security-Policy:** [A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html) * **Access-Control-Allow-Origin:** Used to control which sites are allowed to bypass same origin policies and send cross-origin requests. -* **Strict-Transport-Security:** [Used to control if the browser is allowed to only access a site over a secure connection](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) +* **Strict-Transport-Security:** [Used to control if the browser is allowed to only access a site over a secure connection](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) 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. -### Custom secrets +### 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. + +To edit stored credentials use `bin/rails credentials:edit`. -Rails generates a `config/secrets.yml`. By default, this file contains the -application's `secret_key_base`, but it could also be used to store other -secrets such as access keys for external APIs. +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. -The secrets added to this file are accessible via `Rails.application.secrets`. -For example, with the following `config/secrets.yml`: +The credentials added to this file are accessible via `Rails.application.credentials`. +For example, with the following decrypted `config/credentials.yml.enc`: - development: - secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 - some_api_key: SOMEKEY + secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 + some_api_key: SOMEKEY -`Rails.application.secrets.some_api_key` returns `SOMEKEY` in the development -environment. +`Rails.application.credentials.some_api_key` returns `SOMEKEY` in any environment. If you want an exception to be raised when some key is blank, use the bang version: ```ruby -Rails.application.secrets.some_api_key! # => raises KeyError: key not found: :some_api_key +Rails.application.credentials.some_api_key! # => raises KeyError: :some_api_key is blank ``` Additional Resources @@ -1054,6 +1137,7 @@ Additional Resources The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here: -* Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security) -* [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too) -* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet) +* Subscribe to the Rails security [mailing list](https://groups.google.com/forum/#!forum/rubyonrails-security). +* [Brakeman - Rails Security Scanner](https://brakemanscanner.org/) - To perform static security analysis for Rails applications. +* [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too). +* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet). diff --git a/guides/source/testing.md b/guides/source/testing.md index 6f783089a9..bf310cf2db 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1,14 +1,14 @@ **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** -A Guide to Testing Rails Applications -===================================== +Testing Rails Applications +========================== This guide covers built-in mechanisms in Rails for testing your application. After reading this guide, you will know: * Rails testing terminology. -* How to write unit, functional, and integration tests for your application. +* How to write unit, functional, integration, and system tests for your application. * Other popular testing approaches and plugins. -------------------------------------------------------------------------------- @@ -18,7 +18,7 @@ Why Write Tests for your Rails Applications? Rails makes it super easy to write your tests. It starts by producing skeleton test code while you are creating your models and controllers. -By simply running your Rails tests you can ensure your code adheres to the desired functionality even after some major code refactoring. +By running your Rails tests you can ensure your code adheres to the desired functionality even after some major code refactoring. Rails tests can also simulate browser requests and thus you can test your application's response without having to test it through your browser. @@ -33,18 +33,27 @@ Rails creates a `test` directory for you as soon as you create a Rails project u ```bash $ ls -F test -controllers/ helpers/ mailers/ test_helper.rb -fixtures/ integration/ models/ +controllers/ helpers/ mailers/ system/ test_helper.rb +fixtures/ integration/ models/ application_system_test_case.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 system test directory holds system tests, which are used for full browser +testing of your application. System tests allow you to test your application +the way your users experience it and help you test your JavaScript as well. +System tests inherit from Capybara and perform in browser tests for your +application. + Fixtures are a way of organizing test data; they reside in the `fixtures` directory. A `jobs` directory will also be created when an associated test is first generated. The `test_helper.rb` file holds the default configuration for your tests. +The `application_system_test_case.rb` holds the default configuration for your system +tests. + ### The Test Environment @@ -114,7 +123,7 @@ def test_the_truth end ``` -However only the `test` macro allows a more readable test name. You can still use regular method definitions though. +Although you can still use regular method definitions, using the `test` macro allows for a more readable test name. NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. This may require use of `define_method` and `send` calls to function properly, but formally there's little restriction on the name. @@ -264,7 +273,7 @@ When a test fails you are presented with the corresponding backtrace. By default Rails filters that backtrace and will only print lines relevant to your application. This eliminates the framework noise and helps to focus on your code. However there are situations when you want to see the full -backtrace. Simply set the `-b` (or `--backtrace`) argument to enable this behavior: +backtrace. Set the `-b` (or `--backtrace`) argument to enable this behavior: ```bash $ bin/rails test -b test/models/article_test.rb @@ -310,6 +319,8 @@ specify to make your test failure messages clearer. | `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.| | `assert_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.| | `assert_not_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| +| `assert_in_epsilon ( expected, actual, [epsilon], [msg] )` | Ensures that the numbers `expected` and `actual` have a relative error less than `epsilon`.| +| `assert_not_in_epsilon ( expected, actual, [epsilon], [msg] )` | Ensures that the numbers `expected` and `actual` don't have a relative error less than `epsilon`.| | `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.| | `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| | `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| @@ -341,7 +352,9 @@ Rails adds some custom assertions of its own to the `minitest` framework: | --------------------------------------------------------------------------------- | ------- | | [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| | [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| -| [`assert_nothing_raised { block }`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.| +| [`assert_changes(expressions, message = nil, from:, to:, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_changes) | Test that the result of evaluating an expression is changed after invoking the passed in block.| +| [`assert_no_changes(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_changes) | Test the result of evaluating an expression is not changed after invoking the passed in block.| +| [`assert_nothing_raised { block }`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.| | [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| | [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| @@ -356,8 +369,10 @@ All the basic assertions such as `assert_equal` defined in `Minitest::Assertions * [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html) * [`ActionMailer::TestCase`](http://api.rubyonrails.org/classes/ActionMailer/TestCase.html) * [`ActionView::TestCase`](http://api.rubyonrails.org/classes/ActionView/TestCase.html) -* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html) * [`ActiveJob::TestCase`](http://api.rubyonrails.org/classes/ActiveJob/TestCase.html) +* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html) +* [`ActionDispatch::SystemTestCase`](http://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html) +* [`Rails::Generators::TestCase`](http://api.rubyonrails.org/classes/Rails/Generators/TestCase.html) Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. @@ -439,7 +454,8 @@ You can run multiple files and directories at the same time: By default test failures and errors are reported inline during a run. Rails options: - -e, --environment ENV Run tests in the ENV environment + -w, --warnings Run with Ruby warnings enabled + -e, --environment Run tests in the ENV environment -b, --backtrace Show the complete backtrace -d, --defer-output Output test failures and errors after the test run -f, --fail-fast Abort test run on first failure or error @@ -504,7 +520,7 @@ steve: Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank line. You can place comments in a fixture file by using the # character in the first column. -If you are working with [associations](/association_basics.html), you can simply +If you are working with [associations](/association_basics.html), you can define a reference node between two different fixtures. Here's an example with a `belongs_to`/`has_many` association: @@ -587,6 +603,192 @@ create test/fixtures/articles.yml Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html). +System Testing +-------------- + +System tests allow you to test user interactions with your application, running tests +in either a real or a headless browser. System tests uses Capybara under the hood. + +For creating Rails system tests, you use the `test/system` directory in your +application. Rails provides a generator to create a system test skeleton for you. + +```bash +$ bin/rails generate system_test users + invoke test_unit + create test/system/users_test.rb +``` + +Here's what a freshly generated system test looks like: + +```ruby +require "application_system_test_case" + +class UsersTest < ApplicationSystemTestCase + # test "visiting the index" do + # visit users_url + # + # assert_selector "h1", text: "Users" + # end +end +``` + +By default, system tests are run with the Selenium driver, using the Chrome +browser, and a screen size of 1400x1400. The next section explains how to +change the default settings. + +### Changing the default settings + +Rails makes changing the default settings for system tests very simple. All +the setup is abstracted away so you can focus on writing your tests. + +When you generate a new application or scaffold, an `application_system_test_case.rb` file +is created in the test directory. This is where all the configuration for your +system tests should live. + +If you want to change the default settings you can change what the system +tests are "driven by". Say you want to change the driver from Selenium to +Poltergeist. First add the `poltergeist` gem to your `Gemfile`. Then in your +`application_system_test_case.rb` file do the following: + +```ruby +require "test_helper" +require "capybara/poltergeist" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :poltergeist +end +``` + +The driver name is a required argument for `driven_by`. The optional arguments +that can be passed to `driven_by` are `:using` for the browser (this will only +be used by Selenium), `:screen_size` to change the size of the screen for +screenshots, and `:options` which can be used to set options supported by the +driver. + +```ruby +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :firefox +end +``` + +If you want to use a headless browser, you could use Headless Chrome or Headless Firefox by adding +`headless_chrome` or `headless_firefox` in the `:using` argument. + +```ruby +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :headless_chrome +end +``` + +If your Capybara configuration requires more setup than provided by Rails, this +additional configuration could be added into the `application_system_test_case.rb` +file. + +Please see [Capybara's documentation](https://github.com/teamcapybara/capybara#setup) +for additional settings. + +### Screenshot Helper + +The `ScreenshotHelper` is a helper designed to capture screenshots of your tests. +This can be helpful for viewing the browser at the point a test failed, or +to view screenshots later for debugging. + +Two methods are provided: `take_screenshot` and `take_failed_screenshot`. +`take_failed_screenshot` is automatically included in `after_teardown` inside +Rails. + +The `take_screenshot` helper method can be included anywhere in your tests to +take a screenshot of the browser. + +### Implementing a system test + +Now we're going to add a system test to our blog application. We'll demonstrate +writing a system test by visiting the index page and creating a new blog article. + +If you used the scaffold generator, a system test skeleton was automatically +created for you. If you didn't use the scaffold generator, start by creating a +system test skeleton. + +```bash +$ bin/rails generate system_test articles +``` + +It should have created a test file placeholder for us. With the output of the +previous command you should see: + +```bash + invoke test_unit + create test/system/articles_test.rb +``` + +Now let's open that file and write our first assertion: + +```ruby +require "application_system_test_case" + +class ArticlesTest < ApplicationSystemTestCase + test "viewing the index" do + visit articles_path + assert_selector "h1", text: "Articles" + end +end +``` + +The test should see that there is an `h1` on the articles index page and pass. + +Run the system tests. + +```bash +bin/rails test:system +``` + +NOTE: By default, running `bin/rails test` won't run your system tests. +Make sure to run `bin/rails test:system` to actually run them. + +#### Creating articles system test + +Now let's test the flow for creating a new article in our blog. + +```ruby +test "creating an article" do + visit articles_path + + click_on "New Article" + + fill_in "Title", with: "Creating an Article" + fill_in "Body", with: "Created this article successfully!" + + click_on "Create Article" + + assert_text "Creating an Article" +end +``` + +The first step is to call `visit articles_path`. This will take the test to the +articles index page. + +Then the `click_on "New Article"` will find the "New Article" button on the +index page. This will redirect the browser to `/articles/new`. + +Then the test will fill in the title and body of the article with the specified +text. Once the fields are filled in, "Create Article" is clicked on which will +send a POST request to create the new article in the database. + +We will be redirected back to the the articles index page and there we assert +that the text from the new article's title is on the articles index page. + +#### Taking it further + +The beauty of system testing is that it is similar to integration testing in +that it tests the user's interaction with your controller, model, and view, but +system testing is much more robust and actually tests your application as if +a real user were using it. Going forward, you can test anything that the user +themselves would do in your application such as commenting, deleting articles, +publishing draft articles, etc. Integration Testing ------------------- @@ -601,7 +803,7 @@ $ bin/rails generate integration_test user_flows create test/integration/user_flows_test.rb ``` -Here's what a freshly-generated integration test looks like: +Here's what a freshly generated integration test looks like: ```ruby require 'test_helper' @@ -739,7 +941,7 @@ each of the seven default actions, you can use the following command: $ bin/rails generate test_unit:scaffold article ... invoke test_unit -create test/controllers/articles_controller_test.rb +create test/controllers/articles_controller_test.rb ... ``` @@ -868,16 +1070,16 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest assert_equal "index", @controller.action_name assert_equal "application/x-www-form-urlencoded", @request.media_type - assert_match "Articles", @response.body + assert_match "Articles", @response.body end end ``` ### Setting Headers and CGI variables -[HTTP headers](http://tools.ietf.org/search/rfc2616#section-5.3) +[HTTP headers](https://tools.ietf.org/search/rfc2616#section-5.3) and -[CGI variables](http://tools.ietf.org/search/rfc3875#section-4.1) +[CGI variables](https://tools.ietf.org/search/rfc3875#section-4.1) can be passed as headers: ```ruby @@ -1220,7 +1422,7 @@ In order to test that your mailer is working as expected, you can use unit tests For the purposes of unit testing a mailer, fixtures are used to provide an example of how the output _should_ look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory apart from the other fixtures. The name of the directory within `test/fixtures` directly corresponds to the name of the mailer. So, for a mailer named `UserMailer`, the fixtures should reside in `test/fixtures/user_mailer` directory. -When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator, you'll have to create those files yourself. +If you generated your mailer, the generator does not create stub fixtures for the mailers actions. You'll have to create those files yourself as described above. #### The Basic Test Case @@ -1254,6 +1456,10 @@ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. The helper `read_fixture` is used to read in the content from this file. +NOTE: `email.body.to_s` is present when there's only one (HTML or text) part present. +If the mailer provides both, you can test your fixture against specific parts +with `email.text_part.body.to_s` or `email.html_part.body.to_s`. + Here's the content of the `invite` fixture: ``` @@ -1292,7 +1498,7 @@ class UserControllerTest < ActionDispatch::IntegrationTest 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) + assert_match(/Hi friend@example\.com/, invite_email.body.to_s) end end ``` @@ -1320,7 +1526,7 @@ class BillingJobTest < ActiveJob::TestCase end ``` -This test is pretty simple and only asserts that the job get the work done +This test is pretty simple and only asserts that the job got the work done as expected. By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that diff --git a/guides/source/threading_and_code_execution.md b/guides/source/threading_and_code_execution.md new file mode 100644 index 0000000000..3d3d31b97e --- /dev/null +++ b/guides/source/threading_and_code_execution.md @@ -0,0 +1,324 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Threading and Code Execution in Rails +===================================== + +After reading this guide, you will know: + +* What code Rails will automatically execute concurrently +* How to integrate manual concurrency with Rails internals +* How to wrap all application code +* How to affect application reloading + +-------------------------------------------------------------------------------- + +Automatic Concurrency +--------------------- + +Rails automatically allows various operations to be performed at the same time. + +When using a threaded web server, such as the default Puma, multiple HTTP +requests will be served simultaneously, with each request provided its own +controller instance. + +Threaded Active Job adapters, including the built-in Async, will likewise +execute several jobs at the same time. Action Cable channels are managed this +way too. + +These mechanisms all involve multiple threads, each managing work for a unique +instance of some object (controller, job, channel), while sharing the global +process space (such as classes and their configurations, and global variables). +As long as your code doesn't modify any of those shared things, it can mostly +ignore that other threads exist. + +The rest of this guide describes the mechanisms Rails uses to make it "mostly +ignorable", and how extensions and applications with special needs can use them. + +Executor +-------- + +The Rails Executor separates application code from framework code: any time the +framework invokes code you've written in your application, it will be wrapped by +the Executor. + +The Executor consists of two callbacks: `to_run` and `to_complete`. The Run +callback is called before the application code, and the Complete callback is +called after. + +### Default callbacks + +In a default Rails application, the Executor callbacks are used to: + +* track which threads are in safe positions for autoloading and reloading +* enable and disable the Active Record query cache +* return acquired Active Record connections to the pool +* constrain internal cache lifetimes + +Prior to Rails 5.0, some of these were handled by separate Rack middleware +classes (such as `ActiveRecord::ConnectionAdapters::ConnectionManagement`), or +directly wrapping code with methods like +`ActiveRecord::Base.connection_pool.with_connection`. The Executor replaces +these with a single more abstract interface. + +### Wrapping application code + +If you're writing a library or component that will invoke application code, you +should wrap it with a call to the executor: + +```ruby +Rails.application.executor.wrap do + # call application code here +end +``` + +TIP: If you repeatedly invoke application code from a long-running process, you +may want to wrap using the Reloader instead. + +Each thread should be wrapped before it runs application code, so if your +application manually delegates work to other threads, such as via `Thread.new` +or Concurrent Ruby features that use thread pools, you should immediately wrap +the block: + +```ruby +Thread.new do + Rails.application.executor.wrap do + # your code here + end +end +``` + +NOTE: Concurrent Ruby uses a `ThreadPoolExecutor`, which it sometimes configures +with an `executor` option. Despite the name, it is unrelated. + +The Executor is safely re-entrant; if it is already active on the current +thread, `wrap` is a no-op. + +If it's impractical to wrap the application code in a block (for +example, the Rack API makes this problematic), you can also use the `run!` / +`complete!` pair: + +```ruby +Thread.new do + execution_context = Rails.application.executor.run! + # your code here +ensure + execution_context.complete! if execution_context +end +``` + +### Concurrency + +The Executor will put the current thread into `running` mode in the Load +Interlock. This operation will block temporarily if another thread is currently +either autoloading a constant or unloading/reloading the application. + +Reloader +-------- + +Like the Executor, the Reloader also wraps application code. If the Executor is +not already active on the current thread, the Reloader will invoke it for you, +so you only need to call one. This also guarantees that everything the Reloader +does, including all its callback invocations, occurs wrapped inside the +Executor. + +```ruby +Rails.application.reloader.wrap do + # call application code here +end +``` + +The Reloader is only suitable where a long-running framework-level process +repeatedly calls into application code, such as for a web server or job queue. +Rails automatically wraps web requests and Active Job workers, so you'll rarely +need to invoke the Reloader for yourself. Always consider whether the Executor +is a better fit for your use case. + +### Callbacks + +Before entering the wrapped block, the Reloader will check whether the running +application needs to be reloaded -- for example, because a model's source file has +been modified. If it determines a reload is required, it will wait until it's +safe, and then do so, before continuing. When the application is configured to +always reload regardless of whether any changes are detected, the reload is +instead performed at the end of the block. + +The Reloader also provides `to_run` and `to_complete` callbacks; they are +invoked at the same points as those of the Executor, but only when the current +execution has initiated an application reload. When no reload is deemed +necessary, the Reloader will invoke the wrapped block with no other callbacks. + +### Class Unload + +The most significant part of the reloading process is the Class Unload, where +all autoloaded classes are removed, ready to be loaded again. This will occur +immediately before either the Run or Complete callback, depending on the +`reload_classes_only_on_change` setting. + +Often, additional reloading actions need to be performed either just before or +just after the Class Unload, so the Reloader also provides `before_class_unload` +and `after_class_unload` callbacks. + +### Concurrency + +Only long-running "top level" processes should invoke the Reloader, because if +it determines a reload is needed, it will block until all other threads have +completed any Executor invocations. + +If this were to occur in a "child" thread, with a waiting parent inside the +Executor, it would cause an unavoidable deadlock: the reload must occur before +the child thread is executed, but it cannot be safely performed while the parent +thread is mid-execution. Child threads should use the Executor instead. + +Framework Behavior +------------------ + +The Rails framework components use these tools to manage their own concurrency +needs too. + +`ActionDispatch::Executor` and `ActionDispatch::Reloader` are Rack middlewares +that wraps the request with a supplied Executor or Reloader, respectively. They +are automatically included in the default application stack. The Reloader will +ensure any arriving HTTP request is served with a freshly-loaded copy of the +application if any code changes have occurred. + +Active Job also wraps its job executions with the Reloader, loading the latest +code to execute each job as it comes off the queue. + +Action Cable uses the Executor instead: because a Cable connection is linked to +a specific instance of a class, it's not possible to reload for every arriving +websocket message. Only the message handler is wrapped, though; a long-running +Cable connection does not prevent a reload that's triggered by a new incoming +request or job. Instead, Action Cable uses the Reloader's `before_class_unload` +callback to disconnect all its connections. When the client automatically +reconnects, it will be speaking to the new version of the code. + +The above are the entry points to the framework, so they are responsible for +ensuring their respective threads are protected, and deciding whether a reload +is necessary. Other components only need to use the Executor when they spawn +additional threads. + +### Configuration + +The Reloader only checks for file changes when `cache_classes` is false and +`reload_classes_only_on_change` is true (which is the default in the +`development` environment). + +When `cache_classes` is true (in `production`, by default), the Reloader is only +a pass-through to the Executor. + +The Executor always has important work to do, like database connection +management. When `cache_classes` and `eager_load` are both true (`production`), +no autoloading or class reloading will occur, so it does not need the Load +Interlock. If either of those are false (`development`), then the Executor will +use the Load Interlock to ensure constants are only loaded when it is safe. + +Load Interlock +-------------- + +The Load Interlock allows autoloading and reloading to be enabled in a +multi-threaded runtime environment. + +When one thread is performing an autoload by evaluating the class definition +from the appropriate file, it is important no other thread encounters a +reference to the partially-defined constant. + +Similarly, it is only safe to perform an unload/reload when no application code +is in mid-execution: after the reload, the `User` constant, for example, may +point to a different class. Without this rule, a poorly-timed reload would mean +`User.new.class == User`, or even `User == User`, could be false. + +Both of these constraints are addressed by the Load Interlock. It keeps track of +which threads are currently running application code, loading a class, or +unloading autoloaded constants. + +Only one thread may load or unload at a time, and to do either, it must wait +until no other threads are running application code. If a thread is waiting to +perform a load, it doesn't prevent other threads from loading (in fact, they'll +cooperate, and each perform their queued load in turn, before all resuming +running together). + +### `permit_concurrent_loads` + +The Executor automatically acquires a `running` lock for the duration of its +block, and autoload knows when to upgrade to a `load` lock, and switch back to +`running` again afterwards. + +Other blocking operations performed inside the Executor block (which includes +all application code), however, can needlessly retain the `running` lock. If +another thread encounters a constant it must autoload, this can cause a +deadlock. + +For example, assuming `User` is not yet loaded, the following will deadlock: + +```ruby +Rails.application.executor.wrap do + th = Thread.new do + Rails.application.executor.wrap do + User # inner thread waits here; it cannot load + # User while another thread is running + end + end + + th.join # outer thread waits here, holding 'running' lock +end +``` + +To prevent this deadlock, the outer thread can `permit_concurrent_loads`. By +calling this method, the thread guarantees it will not dereference any +possibly-autoloaded constant inside the supplied block. The safest way to meet +that promise is to put it as close as possible to the blocking call: + +```ruby +Rails.application.executor.wrap do + th = Thread.new do + Rails.application.executor.wrap do + User # inner thread can acquire the load lock, + # load User, and continue + end + end + + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + th.join # outer thread waits here, but has no lock + end +end +``` + +Another example, using Concurrent Ruby: + +```ruby +Rails.application.executor.wrap do + futures = 3.times.collect do |i| + Concurrent::Future.execute do + Rails.application.executor.wrap do + # do work here + end + end + end + + values = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + futures.collect(&:value) + end +end +``` + + +### ActionDispatch::DebugLocks + +If your application is deadlocking and you think the Load Interlock may be +involved, you can temporarily add the ActionDispatch::DebugLocks middleware to +`config/application.rb`: + +```ruby +config.middleware.insert_before Rack::Sendfile, + ActionDispatch::DebugLocks +``` + +If you then restart the application and re-trigger the deadlock condition, +`/rails/locks` will show a summary of all threads currently known to the +interlock, which lock level they are holding or awaiting, and their current +backtrace. + +Generally a deadlock will be caused by the interlock conflicting with some other +external lock or blocking I/O call. Once you find it, you can wrap it with +`permit_concurrent_loads`. + diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 8a3b3b84b4..bb4ef26876 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -1,7 +1,7 @@ **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** -A Guide for Upgrading Ruby on Rails -=================================== +Upgrading Ruby on Rails +======================= This guide provides steps to be followed when you upgrade your applications to a newer version of Ruby on Rails. These steps are also available in individual release guides. @@ -27,7 +27,7 @@ The process should go as follows: 3. Fix tests and deprecated features. 4. Move to the latest patch version of the next minor version. -Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. +Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the `Gemfile` (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. You can find a list of all released Rails versions [here](https://rubygems.org/gems/rails/versions). @@ -45,7 +45,7 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp ### The Update Task Rails provides the `app:update` task (`rake rails:update` on 4.2 and earlier). After updating the Rails version -in the Gemfile, run this task. +in the `Gemfile`, run this task. This will help you with the creation of new files and changes of old files in an interactive session. @@ -65,6 +65,41 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh] Don't forget to review the difference, to see if there were any unexpected changes. +Upgrading from Rails 5.0 to Rails 5.1 +------------------------------------- + +For more information on changes made to Rails 5.1 please see the [release notes](5_1_release_notes.html). + +### Top-level `HashWithIndifferentAccess` is soft-deprecated + +If your application uses the the top-level `HashWithIndifferentAccess` class, you +should slowly move your code to instead use `ActiveSupport::HashWithIndifferentAccess`. + +It is only soft-deprecated, which means that your code will not break at the +moment and no deprecation warning will be displayed, but this constant will be +removed in the future. + +Also, if you have pretty old YAML documents containing dumps of such objects, +you may need to load and dump them again to make sure that they reference +the right constant, and that loading them won't break in the future. + +### `application.secrets` now loaded with all keys as symbols + +If your application stores nested configuration in `config/secrets.yml`, all keys +are now loaded as symbols, so access using strings should be changed. + +From: + +```ruby +Rails.application.secrets[:smtp_settings]["address"] +``` + +To: + +```ruby +Rails.application.secrets[:smtp_settings][:address] +``` + Upgrading from Rails 4.2 to Rails 5.0 ------------------------------------- @@ -140,13 +175,23 @@ See [#19034](https://github.com/rails/rails/pull/19034) for more details. ### Rails Controller Testing +#### Extraction of some helper methods to `rails-controller-testing` + `assigns` and `assert_template` have been extracted to the `rails-controller-testing` gem. To continue using these methods in your controller tests, add `gem 'rails-controller-testing'` to -your Gemfile. +your `Gemfile`. If you are using Rspec for testing, please see the extra configuration required in the gem's documentation. +#### New behavior when uploading files + +If you are using `ActionDispatch::Http::UploadedFile` in your tests to +upload files, you will need to change to use the similar `Rack::Test::UploadedFile` +class instead. + +See [#26404](https://github.com/rails/rails/issues/26404) for more details. + ### Autoloading is Disabled After Booting in the Production Environment Autoloading is now disabled after booting in the production environment by @@ -167,7 +212,7 @@ true. `ActiveModel::Serializers::Xml` has been extracted from Rails to the `activemodel-serializers-xml` gem. To continue using XML serialization in your application, add `gem 'activemodel-serializers-xml'` -to your Gemfile. +to your `Gemfile`. ### Removed Support for Legacy `mysql` Database Adapter @@ -193,7 +238,7 @@ Run `bin/rails` to see the list of commands available. ### `ActionController::Parameters` No Longer Inherits from `HashWithIndifferentAccess` Calling `params` in your application will now return an object instead of a hash. If your -parameters are already permitted, then you will not need to make any changes. If you are using `slice` +parameters are already permitted, then you will not need to make any changes. If you are using `map` and other methods that depend on being able to read the hash regardless of `permitted?` you will need to upgrade your application to first permit and then convert to a hash. @@ -231,6 +276,16 @@ You can now just call the dependency once with a wildcard. <% # Template Dependency: recordings/threads/events/* %> ``` +### `ActionView::Helpers::RecordTagHelper` moved to external gem (record_tag_helper) + +`content_tag_for` and `div_for` have been removed in favor of just using `content_tag`. To continue using the older methods, add the `record_tag_helper` gem to your `Gemfile`: + +```ruby +gem 'record_tag_helper', '~> 1.0' +``` + +See [#18411](https://github.com/rails/rails/pull/18411) for more details. + ### Removed Support for `protected_attributes` Gem The `protected_attributes` gem is no longer supported in Rails 5. @@ -342,16 +397,25 @@ When using Ruby 2.4, you can preserve the timezone of the receiver when calling ActiveSupport.to_time_preserves_timezone = false +### Changes with JSON/JSONB serialization + +In Rails 5.0, how JSON/JSONB attributes are serialized and deserialized changed. Now, if +you set a column equal to a `String`, Active Record will no longer turn that string +into a `Hash`, and will instead only return the string. This is not limited to code +interacting with models, but also affects `:default` column settings in `db/schema.rb`. +It is recommended that you do not set columns equal to a `String`, but pass a `Hash` +instead, which will be converted to and from a JSON string automatically. + Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- ### Web Console -First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. +First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your `Gemfile` and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. ### Responders -`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your Gemfile. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies: +`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your `Gemfile`. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies: ```ruby # app/controllers/users_controller.rb @@ -495,7 +559,7 @@ Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more The documentation for `PermitScrubber` and `TargetScrubber` explains how you can gain complete control over when and how elements should be stripped. -If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your Gemfile: +If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your `Gemfile`: ```ruby gem 'rails-deprecated_sanitizer' @@ -553,7 +617,7 @@ migration DSL counterpart. The migration procedure is as follows: -1. remove `gem "foreigner"` from the Gemfile. +1. remove `gem "foreigner"` from the `Gemfile`. 2. run `bundle install`. 3. run `bin/rake db:schema:dump`. 4. make sure that `db/schema.rb` contains every foreign key definition with @@ -703,9 +767,9 @@ There are a few major changes related to JSON handling in Rails 4.1. MultiJSON has reached its [end-of-life](https://github.com/rails/rails/pull/10576) and has been removed from Rails. -If your application currently depend on MultiJSON directly, you have a few options: +If your application currently depends on MultiJSON directly, you have a few options: -1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future +1. Add 'multi_json' to your `Gemfile`. Note that this might cease to work in the future 2. Migrate away from MultiJSON by using `obj.to_json`, and `JSON.parse(str)` instead. @@ -746,7 +810,7 @@ part of the rewrite, the following features have been removed from the encoder: If your application depends on one of these features, you can get them back by adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder) -gem to your Gemfile. +gem to your `Gemfile`. #### JSON representation of Time objects @@ -1042,7 +1106,7 @@ on the Rails blog. The errata for the `PATCH` verb [specifies that a 'diff' media type should be used with `PATCH`](http://www.rfc-editor.org/errata_search.php?rfc=5789). One -such format is [JSON Patch](http://tools.ietf.org/html/rfc6902). While Rails +such format is [JSON Patch](https://tools.ietf.org/html/rfc6902). While Rails does not support JSON Patch natively, it's easy enough to add support: ``` @@ -1071,8 +1135,8 @@ full support for the last few changes in the specification. ### Gemfile -Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that -line from your Gemfile when upgrading. You should also update your application +Rails 4.0 removed the `assets` group from `Gemfile`. You'd need to remove that +line from your `Gemfile` when upgrading. You should also update your application file (in `config/application.rb`): ```ruby @@ -1083,7 +1147,7 @@ Bundler.require(*Rails.groups) ### vendor/plugins -Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. +Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. ### Active Record @@ -1150,7 +1214,7 @@ end ### Active Resource -Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. +Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your `Gemfile`. ### Active Model @@ -1246,7 +1310,7 @@ get 'こんにちは', controller: 'welcome', action: 'index' get '/' => 'root#index' ``` -* Rails 4.0 has removed `ActionDispatch::BestStandardsSupport` middleware, `<!DOCTYPE html>` already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers`. +* Rails 4.0 has removed `ActionDispatch::BestStandardsSupport` middleware, `<!DOCTYPE html>` already triggers standards mode per https://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers`. Remember you must also remove any references to the middleware from your application code, for example: @@ -1350,7 +1414,7 @@ config.active_record.mass_assignment_sanitizer = :strict ### vendor/plugins -Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. +Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. ### Active Record diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index c1dfcab6f3..c3dff1772c 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -24,11 +24,11 @@ In order to understand Ajax, you must first understand what a web browser does normally. When you type `http://localhost:3000` into your browser's address bar and hit -'Go,' the browser (your 'client') makes a request to the server. It parses the +'Go', the browser (your 'client') makes a request to the server. It parses the response, then fetches all associated assets, like JavaScript files, stylesheets and images. It then assembles the page. If you click a link, it does the same process: fetch the page, fetch the assets, put it all together, -show you the results. This is called the 'request response cycle.' +show you the results. This is called the 'request response cycle'. JavaScript can also make requests to the server, and parse the response. It also has the ability to update information on the page. Combining these two @@ -57,7 +57,7 @@ will show you how Rails can help you write websites in this way, but it's all built on top of this fairly simple technique. Unobtrusive JavaScript -------------------------------------- +---------------------- Rails uses a technique called "Unobtrusive JavaScript" to handle attaching JavaScript to the DOM. This is generally considered to be a best-practice @@ -139,7 +139,9 @@ JavaScript) in this style, and you can expect that many libraries will also follow this pattern. Built-in Helpers ----------------------- +---------------- + +### Remote elements Rails provides a bunch of view helper methods written in Ruby to assist you in generating HTML. Sometimes, you want to add a little Ajax to those elements, @@ -149,18 +151,22 @@ Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two parts: the JavaScript half and the Ruby half. Unless you have disabled the Asset Pipeline, -[rails.js](https://github.com/rails/jquery-ujs/blob/master/src/rails.js) +[rails-ujs](https://github.com/rails/rails/tree/master/actionview/app/assets/javascripts) provides the JavaScript half, and the regular Ruby view helpers add appropriate tags to your DOM. -### form_for +You can read below about the different events that are fired dealing with +remote elements inside your application. -[`form_for`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for) -is a helper that assists with writing forms. `form_for` takes a `:remote` -option. It works like this: +#### form_with + +[`form_with`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with) +is a helper that assists with writing forms. By default, `form_with` assumes that +your form will be using Ajax. You can opt out of this behavior by +passing the `:local` option `form_with`. ```erb -<%= form_for(@article, remote: true) do |f| %> +<%= form_with(model: @article) do |f| %> ... <% end %> ``` @@ -168,7 +174,7 @@ option. It works like this: This will generate the following HTML: ```html -<form accept-charset="UTF-8" action="/articles" class="new_article" data-remote="true" id="new_article" method="post"> +<form action="/articles" accept-charset="UTF-8" method="post" data-remote="true"> ... </form> ``` @@ -182,39 +188,21 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out: ```coffeescript $(document).ready -> - $("#new_article").on("ajax:success", (e, data, status, xhr) -> + $("#new_article").on("ajax:success", (event) -> + [data, status, xhr] = event.detail $("#new_article").append xhr.responseText - ).on "ajax:error", (e, xhr, status, error) -> + ).on "ajax:error", (event) -> $("#new_article").append "<p>ERROR</p>" ``` Obviously, you'll want to be a bit more sophisticated than that, but it's a -start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax). - -### form_tag - -[`form_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag) -is very similar to `form_for`. It has a `:remote` option that you can use like -this: - -```erb -<%= form_tag('/articles', remote: true) do %> - ... -<% end %> -``` - -This will generate the following HTML: - -```html -<form accept-charset="UTF-8" action="/articles" data-remote="true" method="post"> - ... -</form> -``` +start. -Everything else is the same as `form_for`. See its documentation for full -details. +NOTE: As of Rails 5.1 and the new `rails-ujs`, the parameters `data, status, xhr` +have been bundled into `event.detail`. For information about the previously used +`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax). -### link_to +#### link_to [`link_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) is a helper that assists with generating links. It has a `:remote` option you @@ -230,7 +218,7 @@ which generates <a href="/articles/1" data-remote="true">an article</a> ``` -You can bind to the same Ajax events as `form_for`. Here's an example. Let's +You can bind to the same Ajax events as `form_with`. Here's an example. Let's assume that we have a list of articles that can be deleted with just one click. We would generate some HTML like this: @@ -242,11 +230,11 @@ and write some CoffeeScript like this: ```coffeescript $ -> - $("a[data-remote]").on "ajax:success", (e, data, status, xhr) -> + $("a[data-remote]").on "ajax:success", (event) -> alert "The article was deleted." ``` -### button_to +#### button_to [`button_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) is a helper that helps you create buttons. It has a `:remote` option that you can call like this: @@ -262,7 +250,148 @@ this generates </form> ``` -Since it's just a `<form>`, all of the information on `form_for` also applies. +Since it's just a `<form>`, all of the information on `form_with` also applies. + +### Customize remote elements + +It is possible to customize the behavior of elements with a `data-remote` +attribute without writing a line of JavaScript. You can specify extra `data-` +attributes to accomplish this. + +#### `data-method` + +Activating hyperlinks always results in an HTTP GET request. However, if your +application is [RESTful](https://en.wikipedia.org/wiki/Representational_State_Transfer), +some links are in fact actions that change data on the server, and must be +performed with non-GET requests. This attribute allows marking up such links +with an explicit method such as "post", "put" or "delete". + +The way it works is that, when the link is activated, it constructs a hidden form +in the document with the "action" attribute corresponding to "href" value of the +link, and the method corresponding to `data-method` value, and submits that form. + +NOTE: Because submitting forms with HTTP methods other than GET and POST isn't +widely supported across browsers, all other HTTP methods are actually sent over +POST with the intended method indicated in the `_method` parameter. Rails +automatically detects and compensates for this. + +#### `data-url` and `data-params` + +Certain elements of your page aren't actually referring to any URL, but you may want +them to trigger Ajax calls. Specifying the `data-url` attribute along with +the `data-remote` one will trigger an Ajax call to the given URL. You can also +specify extra parameters through the `data-params` attribute. + +This can be useful to trigger an action on check-boxes for instance: + +```html +<input type="checkbox" data-remote="true" + data-url="/update" data-params="id=10" data-method="put"> +``` + +#### `data-type` + +It is also possible to define the Ajax `dataType` explicitly while performing +requests for `data-remote` elements, by way of the `data-type` attribute. + +### Confirmations + +You can ask for an extra confirmation of the user by adding a `data-confirm` +attribute on links and forms. The user will be presented a JavaScript `confirm()` +dialog containing the attribute's text. If the user chooses to cancel, the action +doesn't take place. + +Adding this attribute on links will trigger the dialog on click, and adding it +on forms will trigger it on submit. For example: + +```erb +<%= link_to "Dangerous zone", dangerous_zone_path, + data: { confirm: 'Are you sure?' } %> +``` + +This generates: + +```html +<a href="..." data-confirm="Are you sure?">Dangerous zone</a> +``` + +The attribute is also allowed on form submit buttons. This allows you to customize +the warning message depending on the button which was activated. In this case, +you should **not** have `data-confirm` on the form itself. + +The default confirmation uses a JavaScript confirm dialog, but you can customize +this by listening to the `confirm` event, which is fired just before the confirmation +window appears to the user. To cancel this default confirmation, have the confirm +handler to return `false`. + +### Automatic disabling + +It is also possible to automatically disable an input while the form is submitting +by using the `data-disable-with` attribute. This is to prevent accidental +double-clicks from the user, which could result in duplicate HTTP requests that +the backend may not detect as such. The value of the attribute is the text that will +become the new value of the button in its disabled state. + +This also works for links with `data-method` attribute. + +For example: + +```erb +<%= form_with(model: @article.new) do |f| %> + <%= f.submit data: { "disable-with": "Saving..." } %> +<%= end %> +``` + +This generates a form with: + +```html +<input data-disable-with="Saving..." type="submit"> +``` + +### Rails-ujs event handlers + +Rails 5.1 introduced rails-ujs and dropped jQuery as a dependency. +As a result the Unobtrusive JavaScript (UJS) driver has been rewritten to operate without jQuery. +These introductions cause small changes to `custom events` fired during the request: + +NOTE: Signature of calls to UJS's event handlers has changed. +Unlike the version with jQuery, all custom events return only one parameter: `event`. +In this parameter, there is an additional attribute `detail` which contains an array of extra parameters. + +| Event name | Extra parameters (event.detail) | Fired | +|---------------------|---------------------------------|-------------------------------------------------------------| +| `ajax:before` | | Before the whole ajax business. | +| `ajax:beforeSend` | [xhr, options] | Before the request is sent. | +| `ajax:send` | [xhr] | When the request is sent. | +| `ajax:stopped` | | When the request is stopped. | +| `ajax:success` | [response, status, xhr] | After completion, if the response was a success. | +| `ajax:error` | [response, status, xhr] | After completion, if the response was an error. | +| `ajax:complete` | [xhr, status] | After the request has been completed, no matter the outcome.| + +Example usage: + +```html +document.body.addEventListener('ajax:success', function(event) { + var detail = event.detail; + var data = detail[0], status = detail[1], xhr = detail[2]; +}) +``` + +NOTE: As of Rails 5.1 and the new `rails-ujs`, the parameters `data, status, xhr` +have been bundled into `event.detail`. For information about the previously used +`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax). + +### Stoppable events + +If you stop `ajax:before` or `ajax:beforeSend` by returning false from the +handler method, the Ajax request will never take place. The `ajax:before` event +can manipulate form data before serialization and the +`ajax:beforeSend` event is useful for adding custom request headers. + +If you stop the `ajax:aborted:file` event, the default behavior of allowing the +browser to submit the form via normal means (i.e. non-Ajax submission) will be +canceled and the form will not be submitted at all. This is useful for +implementing your own Ajax file upload workaround. Server-Side Concerns -------------------- @@ -297,7 +426,7 @@ The index view (`app/views/users/index.html.erb`) contains: <br> -<%= form_for(@user, remote: true) do |f| %> +<%= form_with(model: @user) do |f| %> <%= f.label :name %><br> <%= f.text_field :name %> <%= f.submit %> @@ -338,7 +467,7 @@ this: end ``` -Notice the format.js in the `respond_to` block; that allows the controller to +Notice the `format.js` in the `respond_to` block: that allows the controller to respond to your Ajax request. You then have a corresponding `app/views/users/create.js.erb` view file that generates the actual JavaScript code that will be sent and executed on the client side. @@ -355,7 +484,7 @@ which uses Ajax to speed up page rendering in most applications. ### How Turbolinks Works -Turbolinks attaches a click handler to all `<a>` on the page. If your browser +Turbolinks attaches a click handler to all `<a>` tags on the page. If your browser supports [PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState%28%29_method), Turbolinks will make an Ajax request for the page, parse the response, and @@ -363,7 +492,7 @@ replace the entire `<body>` of the page with the `<body>` of the response. It will then use PushState to change the URL to the correct one, preserving refresh semantics and giving you pretty URLs. -The only thing you have to do to enable Turbolinks is have it in your Gemfile, +The only thing you have to do to enable Turbolinks is have it in your `Gemfile`, and put `//= require turbolinks` in your JavaScript manifest, which is usually `app/assets/javascripts/application.js`. @@ -385,7 +514,7 @@ $(document).ready -> ``` However, because Turbolinks overrides the normal page loading process, the -event that this relies on will not be fired. If you have code that looks like +event that this relies upon will not be fired. If you have code that looks like this, you must change your code to do this instead: ```coffeescript diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb index c0a32c6b91..f38b6c2639 100644 --- a/guides/w3c_validator.rb +++ b/guides/w3c_validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # --------------------------------------------------------------------------- # # This script validates the generated guides against the W3C Validator. @@ -32,7 +34,8 @@ include W3CValidators module RailsGuides class Validator def validate - validator = MarkupValidator.new + # https://github.com/w3c-validators/w3c_validators/issues/25 + validator = NuValidator.new STDOUT.sync = true errors_on_guides = {} @@ -44,11 +47,11 @@ module RailsGuides next end - if results.validity - print "." - else + if results.errors.length > 0 print "E" errors_on_guides[f] = results.errors + else + print "." end end |