diff options
Diffstat (limited to 'guides')
181 files changed, 8947 insertions, 4480 deletions
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 4cfc5b1f10..cd3fa9f65b 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,13 +1,5 @@ -* Fixed missing line and shadow on service pages(404, 422, 500). +* New section in Active Record Querying: Understanding The Method Chaining - *Dmitry Korotkov* + *Andrey Nering* -* Removed repetitive th tags. Instead of them added one th tag with a colspan attribute. - - *Sıtkı Bağdat* - -* Added the Rails maintenance policy to the guides. - - *Matias Korhonen* - -Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes. +Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/Rakefile b/guides/Rakefile index d6dd950d01..fd093b94c1 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -13,8 +13,8 @@ namespace :guides do desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing" task :kindle do - unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ - abort "Please `gem install kindlerb`" + unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ + abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH" end unless `convert` =~ /convert/ abort "Please install ImageMagick`" @@ -34,11 +34,13 @@ namespace :guides do task :help do puts <<-help -Guides are taken from the source directory, and the resulting HTML goes into the +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 part of the generation process. -All this process is handled via rake tasks, here's a full list of them: +You can generate HTML, Kindle or both formats using the `guides:generate` task. + +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: diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png Binary files differnew file mode 100644 index 0000000000..117a78a39f --- /dev/null +++ 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 4a30e49e6d..5b88a842b2 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 1a13eddd91..9755f581a6 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 differnew file mode 100644 index 0000000000..9f32c68472 --- /dev/null +++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png Binary files differdeleted file mode 100644 index 6c78e52173..0000000000 --- a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png +++ /dev/null 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 6910e1647e..98bff37d4a 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 bf23cba231..0566a3ffde 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 differnew file mode 100644 index 0000000000..bd3ae4fa67 --- /dev/null +++ b/guides/assets/images/getting_started/new_article.png diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png Binary files differdeleted file mode 100644 index b20b0192d4..0000000000 --- a/guides/assets/images/getting_started/new_post.png +++ /dev/null diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png Binary files differdeleted file mode 100644 index e13095ff8f..0000000000 --- a/guides/assets/images/getting_started/post_with_comments.png +++ /dev/null diff --git a/guides/assets/images/getting_started/rails_welcome.jpg b/guides/assets/images/getting_started/rails_welcome.jpg Binary files differdeleted file mode 100644 index 65a44cdfe5..0000000000 --- a/guides/assets/images/getting_started/rails_welcome.jpg +++ /dev/null diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png Binary files differnew file mode 100644 index 0000000000..3e07c948a0 --- /dev/null +++ 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 35ee4f348f..ed62862291 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/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png Binary files differindex 1cbddfa0f1..08c54f921f 100644 --- a/guides/assets/images/getting_started/routing_error_no_route_matches.png +++ b/guides/assets/images/getting_started/routing_error_no_route_matches.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 differnew file mode 100644 index 0000000000..4dad704f89 --- /dev/null +++ b/guides/assets/images/getting_started/show_action_for_articles.png diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png Binary files differdeleted file mode 100644 index 9467df6a07..0000000000 --- a/guides/assets/images/getting_started/show_action_for_posts.png +++ /dev/null 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 differnew file mode 100644 index 0000000000..4e636d09ff --- /dev/null +++ b/guides/assets/images/getting_started/template_is_missing_articles_new.png diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png Binary files differdeleted file mode 100644 index f03db05fb8..0000000000 --- a/guides/assets/images/getting_started/template_is_missing_posts_new.png +++ /dev/null diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png Binary files differdeleted file mode 100644 index c29cb2f54f..0000000000 --- a/guides/assets/images/getting_started/undefined_method_post_path.png +++ /dev/null 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 differnew file mode 100644 index 0000000000..fd20cd53dc --- /dev/null +++ b/guides/assets/images/getting_started/unknown_action_create_for_articles.png diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png Binary files differdeleted file mode 100644 index 8fdd4c574a..0000000000 --- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png +++ /dev/null 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 differnew file mode 100644 index 0000000000..e948a51e4a --- /dev/null +++ b/guides/assets/images/getting_started/unknown_action_new_for_articles.png diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png Binary files differdeleted file mode 100644 index 7e72feee38..0000000000 --- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png +++ /dev/null diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js index e4d25dfb21..a9c7f0d016 100644 --- a/guides/assets/javascripts/guides.js +++ b/guides/assets/javascripts/guides.js @@ -51,3 +51,9 @@ var guidesIndex = { window.location = url; } }; + +// Disable autolink inside example code blocks of guides. +$(document).ready(function() { + SyntaxHighlighter.defaults['auto-links'] = false; + SyntaxHighlighter.all(); +}); diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index 898f9ff05b..ed558e4793 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -34,7 +34,7 @@ pre, code { overflow: auto; color: #222; } -pre,tt,code,.note>p { +pre, tt, code { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ @@ -381,9 +381,12 @@ a, a:link, a:visited { font: inherit; padding-left: .75em; font-size: .95em; - background-position: 96% -65px; + background-position: 96% 16px; -webkit-appearance: none; } + .guides-index-small .guides-index-item:hover{ + background-position: 96% -65px; + } } #guides { diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 9387e3dc1d..e04d79c818 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -1,5 +1,5 @@ # Activate the gem you are reporting the issue against. -gem 'rails', '4.0.0' +gem 'rails', '4.2.0' require 'rails' require 'action_controller/railtie' @@ -7,8 +7,8 @@ require 'action_controller/railtie' class TestApp < Rails::Application config.root = File.dirname(__FILE__) config.session_store :cookie_store, key: 'cookie_store_key' - config.secret_token = 'secret_token' - config.secret_key_base = 'secret_key_base' + secrets.secret_token = 'secret_token' + secrets.secret_key_base = 'secret_key_base' config.logger = Logger.new($stdout) Rails.logger = config.logger diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index d44fd9196a..9be8130884 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -2,6 +2,7 @@ unless File.exist?('Gemfile') File.write('Gemfile', <<-GEMFILE) source 'https://rubygems.org' gem 'rails', github: 'rails/rails' + gem 'arel', github: 'rails/arel' GEMFILE system 'bundle' @@ -16,8 +17,8 @@ require 'action_controller/railtie' class TestApp < Rails::Application config.root = File.dirname(__FILE__) config.session_store :cookie_store, key: 'cookie_store_key' - config.secret_token = 'secret_token' - config.secret_key_base = 'secret_key_base' + secrets.secret_token = 'secret_token' + secrets.secret_key_base = 'secret_key_base' config.logger = Logger.new($stdout) Rails.logger = config.logger diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index d72633d0b2..66bbb15afb 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,5 +1,5 @@ # Activate the gem you are reporting the issue against. -gem 'activerecord', '4.0.0' +gem 'activerecord', '4.2.0' require 'active_record' require 'minitest/autorun' require 'logger' diff --git a/guides/code/getting_started/.gitignore b/guides/code/getting_started/.gitignore deleted file mode 100644 index 6a502e997f..0000000000 --- a/guides/code/getting_started/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# See https://help.github.com/articles/ignoring-files for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile '~/.gitignore_global' - -# Ignore bundler config. -/.bundle - -# Ignore the default SQLite database. -/db/*.sqlite3 -/db/*.sqlite3-journal - -# Ignore all logfiles and tempfiles. -/log/*.log -/tmp diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile deleted file mode 100644 index a2155c43b9..0000000000 --- a/guides/code/getting_started/Gemfile +++ /dev/null @@ -1,40 +0,0 @@ -source 'https://rubygems.org' - - -# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.1.0' -# Use sqlite3 as the database for Active Record -gem 'sqlite3' -# Use SCSS for stylesheets -gem 'sass-rails', '~> 4.0.1' -# Use Uglifier as compressor for JavaScript assets -gem 'uglifier', '>= 1.3.0' -# Use CoffeeScript for .js.coffee assets and views -gem 'coffee-rails', '~> 4.0.0' -# See https://github.com/sstephenson/execjs#readme for more supported runtimes -# gem 'therubyracer', platforms: :ruby - -# Use jquery as the JavaScript library -gem 'jquery-rails' -# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks -gem 'turbolinks' -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 2.0' -# bundle exec rake doc:rails generates the API under doc/api. -gem 'sdoc', '~> 0.4.0', group: :doc - -# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/jonleighton/spring -gem 'spring', group: :development - -# Use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.1.2' - -# Use unicorn as the app server -# gem 'unicorn' - -# Use Capistrano for deployment -# gem 'capistrano-rails', group: :development - -# Use debugger -# gem 'debugger', group: [:development, :test] - diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock deleted file mode 100644 index a2ab76c908..0000000000 --- a/guides/code/getting_started/Gemfile.lock +++ /dev/null @@ -1,126 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.1.0) - actionpack (= 4.1.0) - actionview (= 4.1.0) - mail (~> 2.5.4) - actionpack (4.1.0) - actionview (= 4.1.0) - activesupport (= 4.1.0) - rack (~> 1.5.2) - rack-test (~> 0.6.2) - actionview (4.1.0) - activesupport (= 4.1.0) - builder (~> 3.1) - erubis (~> 2.7.0) - activemodel (4.1.0) - activesupport (= 4.1.0) - builder (~> 3.1) - activerecord (4.1.0) - activemodel (= 4.1.0) - activesupport (= 4.1.0) - arel (~> 5.0.0) - activesupport (4.1.0) - i18n (~> 0.6, >= 0.6.9) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.1) - tzinfo (~> 1.1) - arel (5.0.0) - atomic (1.1.14) - builder (3.2.2) - coffee-rails (4.0.1) - coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) - coffee-script (2.2.0) - coffee-script-source - execjs - coffee-script-source (1.6.3) - erubis (2.7.0) - execjs (2.0.2) - hike (1.2.3) - i18n (0.6.9) - jbuilder (2.0.2) - activesupport (>= 3.0.0) - multi_json (>= 1.2.0) - jquery-rails (3.0.4) - railties (>= 3.0, < 5.0) - thor (>= 0.14, < 2.0) - json (1.8.1) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.25.1) - minitest (5.2.1) - multi_json (1.8.4) - polyglot (0.3.3) - rack (1.5.2) - rack-test (0.6.2) - rack (>= 1.0) - rails (4.1.0) - actionmailer (= 4.1.0) - actionpack (= 4.1.0) - actionview (= 4.1.0) - activemodel (= 4.1.0) - activerecord (= 4.1.0) - activesupport (= 4.1.0) - bundler (>= 1.3.0, < 2.0) - railties (= 4.1.0) - sprockets-rails (~> 2.0.0) - railties (4.1.0) - actionpack (= 4.1.0) - activesupport (= 4.1.0) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (10.1.1) - rdoc (4.1.1) - json (~> 1.4) - sass (3.2.13) - sass-rails (4.0.1) - railties (>= 4.0.0, < 5.0) - sass (>= 3.1.10) - sprockets-rails (~> 2.0.0) - sdoc (0.4.0) - json (~> 1.8) - rdoc (~> 4.0, < 5.0) - spring (1.0.0) - sprockets (2.10.1) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.0.1) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (~> 2.8) - sqlite3 (1.3.8) - thor (0.18.1) - thread_safe (0.1.3) - atomic - tilt (1.4.1) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) - turbolinks (2.2.0) - coffee-rails - tzinfo (1.1.0) - thread_safe (~> 0.1) - uglifier (2.4.0) - execjs (>= 0.3.0) - json (>= 1.8.0) - -PLATFORMS - ruby - -DEPENDENCIES - coffee-rails (~> 4.0.0) - jbuilder (~> 2.0) - jquery-rails - rails (= 4.1.0) - sass-rails (~> 4.0.1) - sdoc (~> 0.4.0) - spring - sqlite3 - turbolinks - uglifier (>= 1.3.0) diff --git a/guides/code/getting_started/README.rdoc b/guides/code/getting_started/README.rdoc deleted file mode 100644 index dd4e97e22e..0000000000 --- a/guides/code/getting_started/README.rdoc +++ /dev/null @@ -1,28 +0,0 @@ -== README - -This README would normally document whatever steps are necessary to get the -application up and running. - -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... - - -Please feel free to use a different markup language if you do not plan to run -<tt>rake doc:app</tt>. diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile deleted file mode 100644 index 05de8bb536..0000000000 --- a/guides/code/getting_started/Rakefile +++ /dev/null @@ -1,6 +0,0 @@ -# Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. - -require File.expand_path('../config/application', __FILE__) - -Blog::Application.load_tasks diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js deleted file mode 100644 index 5a4fbaa370..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/application.js +++ /dev/null @@ -1,15 +0,0 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// compiled file. -// -// stub path allows dependency to be excluded from the asset bundle. -// -//= require jquery -//= require jquery_ujs -//= require turbolinks -//= require_tree . diff --git a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee b/guides/code/getting_started/app/assets/javascripts/comments.js.coffee deleted file mode 100644 index 24f83d18bb..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/posts.js.coffee deleted file mode 100644 index 24f83d18bb..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee b/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee deleted file mode 100644 index 24f83d18bb..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/guides/code/getting_started/app/assets/stylesheets/application.css b/guides/code/getting_started/app/assets/stylesheets/application.css deleted file mode 100644 index 3192ec897b..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/application.css +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the top of the - * compiled file, but it's generally better to create a new file per style scope. - * - *= require_self - *= require_tree . - */ diff --git a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss b/guides/code/getting_started/app/assets/stylesheets/comments.css.scss deleted file mode 100644 index e730912783..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the Comments controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/posts.css.scss deleted file mode 100644 index 1a7e15390c..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the posts controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss b/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss deleted file mode 100644 index 77ce11a740..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the welcome controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/controllers/application_controller.rb b/guides/code/getting_started/app/controllers/application_controller.rb deleted file mode 100644 index d83690e1b9..0000000000 --- a/guides/code/getting_started/app/controllers/application_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ApplicationController < ActionController::Base - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. - protect_from_forgery with: :exception -end diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb deleted file mode 100644 index b2d9bcdf7f..0000000000 --- a/guides/code/getting_started/app/controllers/comments_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -class CommentsController < ApplicationController - - http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy - - def create - @post = Post.find(params[:post_id]) - @comment = @post.comments.create(comment_params) - redirect_to post_path(@post) - end - - def destroy - @post = Post.find(params[:post_id]) - @comment = @post.comments.find(params[:id]) - @comment.destroy - redirect_to post_path(@post) - end - - private - - def comment_params - params.require(:comment).permit(:commenter, :body) - end -end diff --git a/guides/code/getting_started/app/controllers/concerns/.keep b/guides/code/getting_started/app/controllers/concerns/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/app/controllers/concerns/.keep +++ /dev/null diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb deleted file mode 100644 index 02689ad67b..0000000000 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ /dev/null @@ -1,53 +0,0 @@ -class PostsController < ApplicationController - - http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] - - def index - @posts = Post.all - end - - def show - @post = Post.find(params[:id]) - end - - def edit - @post = Post.find(params[:id]) - end - - def update - @post = Post.find(params[:id]) - - if @post.update(post_params) - redirect_to action: :show, id: @post.id - else - render 'edit' - end - end - - def new - @post = Post.new - end - - def create - @post = Post.new(post_params) - - if @post.save - redirect_to action: :show, id: @post.id - else - render 'new' - end - end - - def destroy - @post = Post.find(params[:id]) - @post.destroy - - redirect_to action: :index - end - - private - - def post_params - params.require(:post).permit(:title, :text) - end -end diff --git a/guides/code/getting_started/app/controllers/welcome_controller.rb b/guides/code/getting_started/app/controllers/welcome_controller.rb deleted file mode 100644 index f9b859b9c9..0000000000 --- a/guides/code/getting_started/app/controllers/welcome_controller.rb +++ /dev/null @@ -1,4 +0,0 @@ -class WelcomeController < ApplicationController - def index - end -end diff --git a/guides/code/getting_started/app/helpers/application_helper.rb b/guides/code/getting_started/app/helpers/application_helper.rb deleted file mode 100644 index de6be7945c..0000000000 --- a/guides/code/getting_started/app/helpers/application_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module ApplicationHelper -end diff --git a/guides/code/getting_started/app/helpers/comments_helper.rb b/guides/code/getting_started/app/helpers/comments_helper.rb deleted file mode 100644 index 0ec9ca5f2d..0000000000 --- a/guides/code/getting_started/app/helpers/comments_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module CommentsHelper -end diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb deleted file mode 100644 index a7b8cec898..0000000000 --- a/guides/code/getting_started/app/helpers/posts_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module PostsHelper -end diff --git a/guides/code/getting_started/app/helpers/welcome_helper.rb b/guides/code/getting_started/app/helpers/welcome_helper.rb deleted file mode 100644 index eeead45fc9..0000000000 --- a/guides/code/getting_started/app/helpers/welcome_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module WelcomeHelper -end diff --git a/guides/code/getting_started/app/mailers/.keep b/guides/code/getting_started/app/mailers/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/app/mailers/.keep +++ /dev/null diff --git a/guides/code/getting_started/app/models/.keep b/guides/code/getting_started/app/models/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/app/models/.keep +++ /dev/null diff --git a/guides/code/getting_started/app/models/comment.rb b/guides/code/getting_started/app/models/comment.rb deleted file mode 100644 index 4e76c5b5b0..0000000000 --- a/guides/code/getting_started/app/models/comment.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Comment < ActiveRecord::Base - belongs_to :post -end diff --git a/guides/code/getting_started/app/models/concerns/.keep b/guides/code/getting_started/app/models/concerns/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/app/models/concerns/.keep +++ /dev/null diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/post.rb deleted file mode 100644 index 64e0d721fd..0000000000 --- a/guides/code/getting_started/app/models/post.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Post < ActiveRecord::Base - has_many :comments, dependent: :destroy - - validates :title, - presence: true, - length: { minimum: 5 } -end diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb deleted file mode 100644 index 593493339e..0000000000 --- a/guides/code/getting_started/app/views/comments/_comment.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -<p> - <strong>Commenter:</strong> - <%= comment.commenter %> -</p> - -<p> - <strong>Comment:</strong> - <%= comment.body %> -</p> - -<p> - <%= link_to 'Destroy Comment', [comment.post, comment], - method: :delete, - data: { confirm: 'Are you sure?' } %> -</p> diff --git a/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb deleted file mode 100644 index 00cb3a08f0..0000000000 --- a/guides/code/getting_started/app/views/comments/_form.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<%= form_for([@post, @post.comments.build]) do |f| %> - <p> - <%= f.label :commenter %><br /> - <%= f.text_field :commenter %> - </p> - <p> - <%= f.label :body %><br /> - <%= f.text_area :body %> - </p> - <p> - <%= f.submit %> - </p> -<% end %> diff --git a/guides/code/getting_started/app/views/layouts/application.html.erb b/guides/code/getting_started/app/views/layouts/application.html.erb deleted file mode 100644 index 95368c37a3..0000000000 --- a/guides/code/getting_started/app/views/layouts/application.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Blog</title> - <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> - <%= javascript_include_tag "application", "data-turbolinks-track" => true %> - <%= csrf_meta_tags %> -</head> -<body> - -<%= yield %> - -</body> -</html> diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb deleted file mode 100644 index f2f83585e1..0000000000 --- a/guides/code/getting_started/app/views/posts/_form.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<%= form_for @post do |f| %> - <% if @post.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:</h2> - <ul> - <% @post.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> - <p> - <%= f.label :title %><br> - <%= f.text_field :title %> - </p> - - <p> - <%= f.label :text %><br> - <%= f.text_area :text %> - </p> - - <p> - <%= f.submit %> - </p> -<% end %> - diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/posts/edit.html.erb deleted file mode 100644 index 393e7430d0..0000000000 --- a/guides/code/getting_started/app/views/posts/edit.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<h1>Edit post</h1> - -<%= render 'form' %> - -<%= link_to 'Back', action: :index %> diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb deleted file mode 100644 index 7369f0396f..0000000000 --- a/guides/code/getting_started/app/views/posts/index.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -<h1>Listing Posts</h1> -<table> - <tr> - <th>Title</th> - <th>Text</th> - <th></th> - <th></th> - <th></th> - </tr> - -<% @posts.each do |post| %> - <tr> - <td><%= post.title %></td> - <td><%= post.text %></td> - <td><%= link_to 'Show', action: :show, id: post.id %></td> - <td><%= link_to 'Edit', action: :edit, id: post.id %></td> - <td><%= link_to 'Destroy', { action: :destroy, id: post.id }, - method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> -<% end %> -</table> diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/posts/new.html.erb deleted file mode 100644 index efa81038ec..0000000000 --- a/guides/code/getting_started/app/views/posts/new.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<h1>New post</h1> - -<%= render 'form' %> - -<%= link_to 'Back', action: :index %> diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb deleted file mode 100644 index e99e9edbb3..0000000000 --- a/guides/code/getting_started/app/views/posts/show.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -<p> - <strong>Title:</strong> - <%= @post.title %> -</p> - -<p> - <strong>Text:</strong> - <%= @post.text %> -</p> - -<h2>Comments</h2> -<%= render @post.comments %> - -<h2>Add a comment:</h2> -<%= render "comments/form" %> - -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb deleted file mode 100644 index 56be8dd3cc..0000000000 --- a/guides/code/getting_started/app/views/welcome/index.html.erb +++ /dev/null @@ -1,4 +0,0 @@ -<h1>Hello, Rails!</h1> - -<%= link_to "My Blog", controller: "posts" %> -<%= link_to "New Post", new_post_path %> diff --git a/guides/code/getting_started/bin/bundle b/guides/code/getting_started/bin/bundle deleted file mode 100755 index 45cf37fba4..0000000000 --- a/guides/code/getting_started/bin/bundle +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'rubygems' -load Gem.bin_path('bundler', 'bundle') diff --git a/guides/code/getting_started/bin/rails b/guides/code/getting_started/bin/rails deleted file mode 100755 index 728cd85aa5..0000000000 --- a/guides/code/getting_started/bin/rails +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require_relative '../config/boot' -require 'rails/commands' diff --git a/guides/code/getting_started/bin/rake b/guides/code/getting_started/bin/rake deleted file mode 100755 index 17240489f6..0000000000 --- a/guides/code/getting_started/bin/rake +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -require_relative '../config/boot' -require 'rake' -Rake.application.run diff --git a/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru deleted file mode 100644 index ddf869e921..0000000000 --- a/guides/code/getting_started/config.ru +++ /dev/null @@ -1,4 +0,0 @@ -# This file is used by Rack-based servers to start the application. - -require ::File.expand_path('../config/environment', __FILE__) -run Blog::Application diff --git a/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb deleted file mode 100644 index 3d7604b659..0000000000 --- a/guides/code/getting_started/config/application.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.expand_path('../boot', __FILE__) - -require 'rails/all' - -# Require the gems listed in Gemfile, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) - -module Blog - class Application < Rails::Application - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - - # Custom directories with classes and modules you want to be autoloadable. - # config.autoload_paths += %W(#{config.root}/extras) - end -end diff --git a/guides/code/getting_started/config/boot.rb b/guides/code/getting_started/config/boot.rb deleted file mode 100644 index 5e5f0c1fac..0000000000 --- a/guides/code/getting_started/config/boot.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) - -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) diff --git a/guides/code/getting_started/config/database.yml b/guides/code/getting_started/config/database.yml deleted file mode 100644 index 51a4dd459d..0000000000 --- a/guides/code/getting_started/config/database.yml +++ /dev/null @@ -1,25 +0,0 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 - -production: - adapter: sqlite3 - database: db/production.sqlite3 - pool: 5 - timeout: 5000 diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb deleted file mode 100644 index e7e341c960..0000000000 --- a/guides/code/getting_started/config/environment.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Load the Rails application. -require File.expand_path('../application', __FILE__) - -# Initialize the Rails application. -Blog::Application.initialize! diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb deleted file mode 100644 index 7e5692b08b..0000000000 --- a/guides/code/getting_started/config/environments/development.rb +++ /dev/null @@ -1,30 +0,0 @@ -Blog::Application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development - # since you don't have to restart the web server when you make code changes. - config.cache_classes = false - - # Do not eager load code on boot. - config.eager_load = false - - # Show full error reports and disable caching. - config.consider_all_requests_local = true - config.action_controller.perform_caching = false - - # Don't care if the mailer can't send. - config.action_mailer.raise_delivery_errors = false - - # Print deprecation notices to the Rails logger. - config.active_support.deprecation = :log - - # Only use best-standards-support built into browsers. - config.action_dispatch.best_standards_support = :builtin - - # Raise an error on page load if there are pending migrations. - config.active_record.migration_error = :page_load - - # Debug mode disables concatenation and preprocessing of assets. - config.assets.debug = true -end diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb deleted file mode 100644 index 93d44723fb..0000000000 --- a/guides/code/getting_started/config/environments/production.rb +++ /dev/null @@ -1,80 +0,0 @@ -Blog::Application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.cache_classes = true - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. - # config.action_dispatch.rack_cache = true - - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_assets = false - - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier - # config.assets.css_compressor = :sass - - # Whether to fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = false - - # Generate digests for assets URLs. - config.assets.digest = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true - - # Set to :debug to see everything in the log. - config.log_level = :info - - # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true - - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify - - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false - - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new -end diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb deleted file mode 100644 index 00adaa5015..0000000000 --- a/guides/code/getting_started/config/environments/test.rb +++ /dev/null @@ -1,36 +0,0 @@ -Blog::Application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true - - # Do not eager load code on boot. This avoids loading your whole application - # just for the purpose of running a single test. If you are using a tool that - # preloads Rails for running tests, you may have to set it to true. - config.eager_load = false - - # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_assets = true - config.static_cache_control = "public, max-age=3600" - - # Show full error reports and disable caching. - config.consider_all_requests_local = true - config.action_controller.perform_caching = false - - # Raise exceptions instead of rendering exception templates. - config.action_dispatch.show_exceptions = false - - # Disable request forgery protection in test environment. - config.action_controller.allow_forgery_protection = false - - # Tell Action Mailer not to deliver emails to the real world. - # The :test delivery method accumulates sent emails in the - # ActionMailer::Base.deliveries array. - config.action_mailer.delivery_method = :test - - # Print deprecation notices to the stderr. - config.active_support.deprecation = :stderr -end diff --git a/guides/code/getting_started/config/initializers/backtrace_silencers.rb b/guides/code/getting_started/config/initializers/backtrace_silencers.rb deleted file mode 100644 index 59385cdf37..0000000000 --- a/guides/code/getting_started/config/initializers/backtrace_silencers.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. -# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } - -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! diff --git a/guides/code/getting_started/config/initializers/filter_parameter_logging.rb b/guides/code/getting_started/config/initializers/filter_parameter_logging.rb deleted file mode 100644 index 4a994e1e7b..0000000000 --- a/guides/code/getting_started/config/initializers/filter_parameter_logging.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password] diff --git a/guides/code/getting_started/config/initializers/inflections.rb b/guides/code/getting_started/config/initializers/inflections.rb deleted file mode 100644 index ac033bf9dc..0000000000 --- a/guides/code/getting_started/config/initializers/inflections.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Add new inflection rules using the following format. Inflections -# are locale specific, and you may define rules for as many different -# locales as you wish. All of these examples are active by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' -# inflect.uncountable %w( fish sheep ) -# end - -# These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' -# end diff --git a/guides/code/getting_started/config/initializers/locale.rb b/guides/code/getting_started/config/initializers/locale.rb deleted file mode 100644 index d89dac7c6a..0000000000 --- a/guides/code/getting_started/config/initializers/locale.rb +++ /dev/null @@ -1,9 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. -# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. -# Rails.application.config.time_zone = 'Central Time (US & Canada)' - -# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. -# Rails.application.config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] -# Rails.application.config.i18n.default_locale = :de diff --git a/guides/code/getting_started/config/initializers/mime_types.rb b/guides/code/getting_started/config/initializers/mime_types.rb deleted file mode 100644 index 72aca7e441..0000000000 --- a/guides/code/getting_started/config/initializers/mime_types.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Add new mime types for use in respond_to blocks: -# Mime::Type.register "text/richtext", :rtf -# Mime::Type.register_alias "text/html", :iphone diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb deleted file mode 100644 index aaf57731be..0000000000 --- a/guides/code/getting_started/config/initializers/secret_token.rb +++ /dev/null @@ -1,12 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key 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 `rake secret` to generate a secure secret key. - -# Make sure your secret_key_base is kept private -# if you're sharing your code publicly. -Blog::Application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53' diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb deleted file mode 100644 index 3b2ca93ab9..0000000000 --- a/guides/code/getting_started/config/initializers/session_store.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Blog::Application.config.session_store :cookie_store, key: '_blog_session' diff --git a/guides/code/getting_started/config/initializers/wrap_parameters.rb b/guides/code/getting_started/config/initializers/wrap_parameters.rb deleted file mode 100644 index 33725e95fd..0000000000 --- a/guides/code/getting_started/config/initializers/wrap_parameters.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# This file contains settings for ActionController::ParamsWrapper which -# is enabled by default. - -# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. -ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] if respond_to?(:wrap_parameters) -end - -# To enable root element in JSON for ActiveRecord objects. -# ActiveSupport.on_load(:active_record) do -# self.include_root_in_json = true -# end diff --git a/guides/code/getting_started/config/locales/en.yml b/guides/code/getting_started/config/locales/en.yml deleted file mode 100644 index 0653957166..0000000000 --- a/guides/code/getting_started/config/locales/en.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t 'hello' -# -# In views, this is aliased to just `t`: -# -# <%= t('hello') %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - -en: - hello: "Hello world" diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb deleted file mode 100644 index 0155b613a3..0000000000 --- a/guides/code/getting_started/config/routes.rb +++ /dev/null @@ -1,7 +0,0 @@ -Blog::Application.routes.draw do - resources :posts do - resources :comments - end - - root "welcome#index" -end diff --git a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb b/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb deleted file mode 100644 index 602bef31ab..0000000000 --- a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreatePosts < ActiveRecord::Migration - def change - create_table :posts do |t| - t.string :title - t.text :text - - t.timestamps - end - end -end diff --git a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb deleted file mode 100644 index 3e51f9c0f7..0000000000 --- a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateComments < ActiveRecord::Migration - def change - create_table :comments do |t| - t.string :commenter - t.text :body - t.references :post, index: true - - t.timestamps - end - end -end diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb deleted file mode 100644 index 101fe712a1..0000000000 --- a/guides/code/getting_started/db/schema.rb +++ /dev/null @@ -1,33 +0,0 @@ -# encoding: UTF-8 -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 20130122045842) do - - create_table "comments", force: true do |t| - t.string "commenter" - t.text "body" - t.integer "post_id" - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "comments", ["post_id"], name: "index_comments_on_post_id" - - create_table "posts", force: true do |t| - t.string "title" - t.text "text" - t.datetime "created_at" - t.datetime "updated_at" - end - -end diff --git a/guides/code/getting_started/db/seeds.rb b/guides/code/getting_started/db/seeds.rb deleted file mode 100644 index 4edb1e857e..0000000000 --- a/guides/code/getting_started/db/seeds.rb +++ /dev/null @@ -1,7 +0,0 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Examples: -# -# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) -# Mayor.create(name: 'Emanuel', city: cities.first) diff --git a/guides/code/getting_started/lib/assets/.keep b/guides/code/getting_started/lib/assets/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/lib/assets/.keep +++ /dev/null diff --git a/guides/code/getting_started/lib/tasks/.keep b/guides/code/getting_started/lib/tasks/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/lib/tasks/.keep +++ /dev/null diff --git a/guides/code/getting_started/log/.keep b/guides/code/getting_started/log/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/log/.keep +++ /dev/null diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html deleted file mode 100644 index 3265cc8e33..0000000000 --- a/guides/code/getting_started/public/404.html +++ /dev/null @@ -1,60 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>The page you were looking for doesn't exist (404)</title> - <style> - body { - background-color: #EFEFEF; - color: #2E2F30; - text-align: center; - font-family: arial, sans-serif; - } - - div.dialog { - width: 25em; - margin: 4em auto 0 auto; - border: 1px solid #CCC; - border-right-color: #999; - border-left-color: #999; - border-bottom-color: #BBB; - border-top: #B00100 solid 4px; - border-top-left-radius: 9px; - border-top-right-radius: 9px; - background-color: white; - padding: 7px 4em 0 4em; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); - } - - h1 { - font-size: 100%; - color: #730E15; - line-height: 1.5em; - } - - body > p { - width: 33em; - margin: 0 auto 1em; - padding: 1em 0; - background-color: #F7F7F7; - border: 1px solid #CCC; - border-right-color: #999; - border-left-color: #999; - border-bottom-color: #999; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-top-color: #DADADA; - color: #666; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); - } - </style> -</head> - -<body> - <!-- This file lives in public/404.html --> - <div class="dialog"> - <h1>The page you were looking for doesn't exist.</h1> - <p>You may have mistyped the address or the page may have moved.</p> - </div> - <p>If you are the application owner check the logs for more information.</p> -</body> -</html> diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html deleted file mode 100644 index d823a8fc77..0000000000 --- a/guides/code/getting_started/public/422.html +++ /dev/null @@ -1,60 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>The change you wanted was rejected (422)</title> - <style> - body { - background-color: #EFEFEF; - color: #2E2F30; - text-align: center; - font-family: arial, sans-serif; - } - - div.dialog { - width: 25em; - margin: 4em auto 0 auto; - border: 1px solid #CCC; - border-right-color: #999; - border-left-color: #999; - border-bottom-color: #BBB; - border-top: #B00100 solid 4px; - border-top-left-radius: 9px; - border-top-right-radius: 9px; - background-color: white; - padding: 7px 4em 0 4em; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); - } - - h1 { - font-size: 100%; - color: #730E15; - line-height: 1.5em; - } - - body > p { - width: 33em; - margin: 0 auto 1em; - padding: 1em 0; - background-color: #F7F7F7; - border: 1px solid #CCC; - border-right-color: #999; - border-left-color: #999; - border-bottom-color: #999; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-top-color: #DADADA; - color: #666; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); - } - </style> -</head> - -<body> - <!-- This file lives in public/422.html --> - <div class="dialog"> - <h1>The change you wanted was rejected.</h1> - <p>Maybe you tried to change something you didn't have access to.</p> - </div> - <p>If you are the application owner check the logs for more information.</p> -</body> -</html> diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html deleted file mode 100644 index ebf6d4c00c..0000000000 --- a/guides/code/getting_started/public/500.html +++ /dev/null @@ -1,59 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>We're sorry, but something went wrong (500)</title> - <style> - body { - background-color: #EFEFEF; - color: #2E2F30; - text-align: center; - font-family: arial, sans-serif; - } - - div.dialog { - width: 25em; - margin: 4em auto 0 auto; - border: 1px solid #CCC; - border-right-color: #999; - border-left-color: #999; - border-bottom-color: #BBB; - border-top: #B00100 solid 4px; - border-top-left-radius: 9px; - border-top-right-radius: 9px; - background-color: white; - padding: 7px 4em 0 4em; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); - } - - h1 { - font-size: 100%; - color: #730E15; - line-height: 1.5em; - } - - body > p { - width: 33em; - margin: 0 auto 1em; - padding: 1em 0; - background-color: #F7F7F7; - border: 1px solid #CCC; - border-right-color: #999; - border-left-color: #999; - border-bottom-color: #999; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-top-color: #DADADA; - color: #666; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); - } - </style> -</head> - -<body> - <!-- This file lives in public/500.html --> - <div class="dialog"> - <h1>We're sorry, but something went wrong.</h1> - </div> - <p>If you are the application owner check the logs for more information.</p> -</body> -</html> diff --git a/guides/code/getting_started/public/favicon.ico b/guides/code/getting_started/public/favicon.ico deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/public/favicon.ico +++ /dev/null diff --git a/guides/code/getting_started/public/robots.txt b/guides/code/getting_started/public/robots.txt deleted file mode 100644 index 3c9c7c01f3..0000000000 --- a/guides/code/getting_started/public/robots.txt +++ /dev/null @@ -1,5 +0,0 @@ -# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file -# -# To ban all spiders from the entire site uncomment the next two lines: -# User-agent: * -# Disallow: / diff --git a/guides/code/getting_started/test/controllers/.keep b/guides/code/getting_started/test/controllers/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/test/controllers/.keep +++ /dev/null diff --git a/guides/code/getting_started/test/controllers/comments_controller_test.rb b/guides/code/getting_started/test/controllers/comments_controller_test.rb deleted file mode 100644 index 2ec71b4ec5..0000000000 --- a/guides/code/getting_started/test/controllers/comments_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class CommentsControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/guides/code/getting_started/test/controllers/posts_controller_test.rb b/guides/code/getting_started/test/controllers/posts_controller_test.rb deleted file mode 100644 index 7a6ee4f1db..0000000000 --- a/guides/code/getting_started/test/controllers/posts_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class PostsControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/guides/code/getting_started/test/controllers/welcome_controller_test.rb b/guides/code/getting_started/test/controllers/welcome_controller_test.rb deleted file mode 100644 index dff8e9d2c5..0000000000 --- a/guides/code/getting_started/test/controllers/welcome_controller_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class WelcomeControllerTest < ActionController::TestCase - test "should get index" do - get :index - assert_response :success - end - -end diff --git a/guides/code/getting_started/test/fixtures/.keep b/guides/code/getting_started/test/fixtures/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/test/fixtures/.keep +++ /dev/null diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml deleted file mode 100644 index 9e409d8a61..0000000000 --- a/guides/code/getting_started/test/fixtures/comments.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - commenter: MyString - body: MyText - post_id: - -two: - commenter: MyString - body: MyText - post_id: diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml deleted file mode 100644 index 46b01c3bb4..0000000000 --- a/guides/code/getting_started/test/fixtures/posts.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - title: MyString - text: MyText - -two: - title: MyString - text: MyText diff --git a/guides/code/getting_started/test/helpers/.keep b/guides/code/getting_started/test/helpers/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/test/helpers/.keep +++ /dev/null diff --git a/guides/code/getting_started/test/helpers/comments_helper_test.rb b/guides/code/getting_started/test/helpers/comments_helper_test.rb deleted file mode 100644 index 2518c16bd5..0000000000 --- a/guides/code/getting_started/test/helpers/comments_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class CommentsHelperTest < ActionView::TestCase -end diff --git a/guides/code/getting_started/test/helpers/posts_helper_test.rb b/guides/code/getting_started/test/helpers/posts_helper_test.rb deleted file mode 100644 index 48549c2ea1..0000000000 --- a/guides/code/getting_started/test/helpers/posts_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class PostsHelperTest < ActionView::TestCase -end diff --git a/guides/code/getting_started/test/helpers/welcome_helper_test.rb b/guides/code/getting_started/test/helpers/welcome_helper_test.rb deleted file mode 100644 index d6ded5995f..0000000000 --- a/guides/code/getting_started/test/helpers/welcome_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class WelcomeHelperTest < ActionView::TestCase -end diff --git a/guides/code/getting_started/test/integration/.keep b/guides/code/getting_started/test/integration/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/test/integration/.keep +++ /dev/null diff --git a/guides/code/getting_started/test/mailers/.keep b/guides/code/getting_started/test/mailers/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/test/mailers/.keep +++ /dev/null diff --git a/guides/code/getting_started/test/models/.keep b/guides/code/getting_started/test/models/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/test/models/.keep +++ /dev/null diff --git a/guides/code/getting_started/test/models/comment_test.rb b/guides/code/getting_started/test/models/comment_test.rb deleted file mode 100644 index b6d6131a96..0000000000 --- a/guides/code/getting_started/test/models/comment_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class CommentTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/guides/code/getting_started/test/models/post_test.rb b/guides/code/getting_started/test/models/post_test.rb deleted file mode 100644 index 6d9d463a71..0000000000 --- a/guides/code/getting_started/test/models/post_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class PostTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb deleted file mode 100644 index f91a4375dc..0000000000 --- a/guides/code/getting_started/test/test_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -ENV["RAILS_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rails/test_help' - -class ActiveSupport::TestCase - ActiveRecord::Migration.check_pending! - - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all - - # Add more helper methods to be used by all tests here... -end diff --git a/guides/code/getting_started/vendor/assets/javascripts/.keep b/guides/code/getting_started/vendor/assets/javascripts/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/vendor/assets/javascripts/.keep +++ /dev/null diff --git a/guides/code/getting_started/vendor/assets/stylesheets/.keep b/guides/code/getting_started/vendor/assets/stylesheets/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/vendor/assets/stylesheets/.keep +++ /dev/null diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index 9d488a8a15..762ab1c0e2 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -24,11 +24,11 @@ begin require 'redcarpet' rescue LoadError # This can happen if doc:guides is executed in an application. - $stderr.puts('Generating guides requires Redcarpet 2.1.1+.') + $stderr.puts('Generating guides requires Redcarpet 3.2.2+.') $stderr.puts(<<ERROR) if bundler? Please add - gem 'redcarpet', '~> 2.1.1' + gem 'redcarpet', '~> 3.2.2' to the Gemfile, run diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index 760b196abd..a78c2e9fca 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -1,3 +1,5 @@ +require 'yaml' + module RailsGuides module Helpers def guide(name, url, options = {}, &block) @@ -37,7 +39,7 @@ module RailsGuides def author(name, nick, image = 'credits_pic_blank.gif', &block) image = "images/#{image}" - result = content_tag(:img, nil, :src => image, :class => 'left pic', :alt => name, :width => 91, :height => 91) + result = tag(:img, :src => image, :class => 'left pic', :alt => name, :width => 91, :height => 91) result << content_tag(:h3, name) result << content_tag(:p, capture(&block)) content_tag(:div, result, :class => 'clearfix', :id => nick) diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 09eecd5634..32926622e3 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -70,7 +70,7 @@ module Kindle File.open("sections/%03d/_section.txt" % section_idx, 'w') {|f| f.puts title} doc.xpath("//h3[@id]").each_with_index do |h3,item_idx| subsection = h3.inner_text - content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map {|x| x.to_html} + content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map(&:to_html) item = Nokogiri::HTML(h3.to_html + content.join("\n")) item_path = "sections/%03d/%03d.html" % [section_idx, item_idx] add_head_section(item, subsection) diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb index 489aa3ea7a..8a908a4339 100644 --- a/guides/rails_guides/levenshtein.rb +++ b/guides/rails_guides/levenshtein.rb @@ -1,31 +1,39 @@ module RailsGuides module Levenshtein - # Based on the pseudocode in http://en.wikipedia.org/wiki/Levenshtein_distance - def self.distance(s1, s2) - s = s1.unpack('U*') - t = s2.unpack('U*') - m = s.length - n = t.length + # This code is based directly on the Text gem implementation + # Returns a value representing the "cost" of transforming str1 into str2 + def self.distance str1, str2 + s = str1 + t = str2 + n = s.length + m = t.length + max = n/2 - # matrix initialization - d = [] - 0.upto(m) { |i| d << [i] } - 0.upto(n) { |j| d[0][j] = j } + return m if (0 == n) + return n if (0 == m) + return n if (n - m).abs > max - # distance computation - 1.upto(m) do |i| - 1.upto(n) do |j| - cost = s[i] == t[j] ? 0 : 1 - d[i][j] = [ - d[i-1][j] + 1, # deletion - d[i][j-1] + 1, # insertion - d[i-1][j-1] + cost, # substitution - ].min + d = (0..m).to_a + x = nil + + str1.each_char.each_with_index do |char1,i| + e = i+1 + + str2.each_char.each_with_index do |char2,j| + cost = (char1 == char2) ? 0 : 1 + x = [ + d[j+1] + 1, # insertion + e + 1, # deletion + d[j] + cost # substitution + ].min + d[j] = e + e = x end + + d[m] = x end - # all done - return d[m][n] + return x end end end diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 547c6d2c15..17035069d0 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -47,8 +47,12 @@ module RailsGuides end def dom_id_text(text) - text.downcase.gsub(/\?/, '-questionmark').gsub(/!/, '-bang').gsub(/[^a-z0-9]+/, ' ') - .strip.gsub(/\s+/, '-') + escaped_chars = Regexp.escape('\\/`*_{}[]()#+-.!:,;|&<>^~=\'"') + + text.downcase.gsub(/\?/, '-questionmark') + .gsub(/!/, '-bang') + .gsub(/[#{escaped_chars}]+/, ' ').strip + .gsub(/\s+/, '-') end def engine @@ -79,10 +83,10 @@ module RailsGuides def generate_structure @headings_for_index = [] if @body.present? - @body = Nokogiri::HTML(@body).tap do |doc| + @body = Nokogiri::HTML.fragment(@body).tap do |doc| hierarchy = [] - doc.at('body').children.each do |node| + doc.children.each do |node| if node.name =~ /^h[3-6]$/ case node.name when 'h3' @@ -116,7 +120,7 @@ module RailsGuides end end - @index = Nokogiri::HTML(engine.render(raw_index)).tap do |doc| + @index = Nokogiri::HTML.fragment(engine.render(raw_index)).tap do |doc| doc.at('ol')[:class] = 'chapters' end.to_html @@ -130,8 +134,8 @@ module RailsGuides end def generate_title - if heading = Nokogiri::HTML(@header).at(:h2) - @title = "#{heading.text} — Ruby on Rails Guides".html_safe + if heading = Nokogiri::HTML.fragment(@header).at(:h2) + @title = "#{heading.text} — Ruby on Rails Guides" else @title = "Ruby on Rails Guides" end diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 2eb7ca17a3..554d94ad50 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -23,8 +23,9 @@ HTML end def paragraph(text) - if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)/ + if 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> (.+)$/ linkback = %(<a href="#footnote-#{$1}-ref"><sup>#{$1}</sup></a>) %(<p class="footnote" id="footnote-#{$1}">#{linkback} #{$2}</p>) @@ -47,10 +48,10 @@ HTML case code_type when 'ruby', 'sql', 'plain' code_type - when 'erb' + when 'erb', 'html+erb' 'ruby; html-script: true' when 'html' - 'xml' # html is understood, but there are .xml rules in the CSS + 'xml' # HTML is understood, but there are .xml rules in the CSS else 'plain' end diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md index 522f628a7e..be00087f63 100644 --- a/guides/source/2_2_release_notes.md +++ b/guides/source/2_2_release_notes.md @@ -1,7 +1,9 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 2.2 Release Notes =============================== -Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub. Along with Rails, 2.2 marks the launch of the [Ruby on Rails Guides](http://guides.rubyonrails.org/), the first results of the ongoing [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide). This site will deliver high-quality documentation of the major features of Rails. diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 8c633fa169..328656f4a4 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -1,14 +1,16 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 2.3 Release Notes =============================== -Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/master) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components. +Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-3-stable) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components. -------------------------------------------------------------------------------- Application Architecture ------------------------ -There are two major changes in the architecture of Rails applications: complete integration of the [Rack](http://rack.rubyforge.org/) 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](http://rack.github.io/) modular web server interface, and renewed support for Rails Engines. ### Rack Integration @@ -594,7 +596,7 @@ The internals of the various <code>rake gem</code> tasks have been substantially * Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding `--with-dispatchers` when you run the `rails` command, or add them later with `rake rails:update:generate_dispatchers`). * Rails Guides have been converted from AsciiDoc to Textile markup. * Scaffolded views and controllers have been cleaned up a bit. -* `script/server` now accepts a <tt>--path</tt> argument to mount a Rails application from a specific path. +* `script/server` now accepts a `--path` argument to mount a Rails application from a specific path. * If any configured gems are missing, the gem rake tasks will skip loading much of the environment. This should solve many of the "chicken-and-egg" problems where rake gems:install couldn't run because gems were missing. * Gems are now unpacked exactly once. This fixes issues with gems (hoe, for instance) which are packed with read-only permissions on the files. @@ -618,4 +620,4 @@ A few pieces of older code are deprecated in this release: Credits ------- -Release notes compiled by [Mike Gunderloy](http://afreshcup.com.) This version of the Rails 2.3 release notes was compiled based on RC2 of Rails 2.3. +Release notes compiled by [Mike Gunderloy](http://afreshcup.com). This version of the Rails 2.3 release notes was compiled based on RC2 of Rails 2.3. diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index cf9d694de7..9ad32e8168 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 3.0 Release Notes =============================== @@ -15,7 +17,7 @@ Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is g On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices. -These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/3-0-stable) in the main Rails repository on GitHub. -------------------------------------------------------------------------------- @@ -138,7 +140,7 @@ More Information: - [Rails Edge Architecture](http://yehudakatz.com/2009/06/11/r [Arel](http://github.com/brynary/arel) (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. -More information: - [Why I wrote Arel](http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/.) +More information: - [Why I wrote Arel](https://web.archive.org/web/20120718093140/http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/) ### Mail Extraction @@ -294,11 +296,11 @@ NOTE. The old style `map` commands still work as before with a backwards compati Deprecations * The catch all route for non-REST applications (`/:controller/:action/:id`) is now commented out. -* Routes :path\_prefix no longer exists and :name\_prefix now automatically adds "\_" at the end of the given value. +* Routes `:path_prefix` no longer exists and `:name_prefix` now automatically adds "_" at the end of the given value. More Information: * [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/) -* [Revamped Routes in Rails 3](http://rizwanreza.com/2009/12/20/revamped-routes-in-rails-3) +* [Revamped Routes in Rails 3](https://medium.com/fusion-of-thoughts/revamped-routes-in-rails-3-b6d00654e5b0) * [Generic Actions in Rails 3](http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/) @@ -308,7 +310,7 @@ More Information: Major re-write was done in the Action View helpers, implementing Unobtrusive JavaScript (UJS) hooks and removing the old inline AJAX commands. This enables Rails to use any compliant UJS driver to implement the UJS hooks in the helpers. -What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper.) To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example: +What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example: ```ruby form_for @post, :remote => true @@ -521,7 +523,7 @@ A large effort was made in Active Support to make it cherry pickable, that is, y These are the main changes in Active Support: * Large clean up of the library removing unused methods throughout. -* Active Support no longer provides vendored versions of [TZInfo](http://tzinfo.rubyforge.org/), [Memcache Client](http://deveiate.org/projects/RMemCache/) and [Builder](http://builder.rubyforge.org/,) these are all included as dependencies and installed via the `bundle install` command. +* Active Support no longer provides vendored versions of TZInfo, Memcache Client and Builder. These are all included as dependencies and installed via the `bundle install` command. * Safe buffers are implemented in `ActiveSupport::SafeBuffer`. * Added `Array.uniq_by` and `Array.uniq_by!`. * Removed `Array#rand` and backported `Array#sample` from Ruby 1.9. @@ -545,7 +547,7 @@ These are the main changes in Active Support: * `String#to_time` and `String#to_datetime` handle fractional seconds. * Added support to new callbacks for around filter object that respond to `:before` and `:after` used in before and after callbacks. * The `ActiveSupport::OrderedHash#to_a` method returns an ordered set of arrays. Matches Ruby 1.9's `Hash#to_a`. -* `MissingSourceFile` exists as a constant but it is now just equals to `LoadError`. +* `MissingSourceFile` exists as a constant but it is now just equal to `LoadError`. * Added `Class#class_attribute`, to be able to declare a class-level attribute whose value is inheritable and overwritable by subclasses. * Finally removed `DeprecatedCallbacks` in `ActiveRecord::Associations`. * `Object#metaclass` is now `Kernel#singleton_class` to match Ruby. @@ -574,7 +576,7 @@ The following methods have been removed because they are no longer used in the f Action Mailer ------------- -Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the Email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. +Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. * All mailers are now in `app/mailers` by default. * Can now send email using new API with three methods: `attachments`, `headers` and `mail`. @@ -608,4 +610,4 @@ Credits See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails 3. Kudos to all of them. -Rails 3.0 Release Notes were compiled by [Mikel Lindsaar](http://lindsaar.net.) +Rails 3.0 Release Notes were compiled by [Mikel Lindsaar](http://lindsaar.net). diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index 485f8c756b..e187e5f9ab 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 3.1 Release Notes =============================== @@ -8,7 +10,10 @@ Highlights in Rails 3.1: * Assets Pipeline * jQuery as the default JavaScript library -This release notes cover the major changes, but don't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +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/3-1-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- @@ -173,7 +178,7 @@ The assets pipeline is powered by [Sprockets](https://github.com/sstephenson/spr ### HTTP Streaming -HTTP Streaming is another change that is new in Rails 3.1. This lets the browser download your stylesheets and JavaScript files while the server is still generating the response. This requires Ruby 1.9.2, is opt-in and requires support from the web server as well, but the popular combo of nginx and unicorn is ready to take advantage of it. +HTTP Streaming is another change that is new in Rails 3.1. This lets the browser download your stylesheets and JavaScript files while the server is still generating the response. This requires Ruby 1.9.2, is opt-in and requires support from the web server as well, but the popular combo of NGINX and Unicorn is ready to take advantage of it. ### Default JS library is now jQuery diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index ce811a583b..6ddf77d9c0 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 3.2 Release Notes =============================== @@ -8,7 +10,10 @@ Highlights in Rails 3.2: * Automatic Query Explains * Tagged Logging -These release notes cover the major changes, but do not include each bug-fix and changes. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/3-2-stable) in the main Rails repository on GitHub. +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/3-2-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- @@ -187,7 +192,7 @@ Action Pack Rails will use `layouts/single_car` when a request comes in `:show` action, and use `layouts/application` (or `layouts/cars`, if exists) when a request comes in for any other actions. -* `form\_for` is changed to use `#{action}\_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}\_#{action}`. +* `form_for` is changed to use `#{action}_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}_#{action}`. * `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`. @@ -562,4 +567,4 @@ 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. -Rails 3.2 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev.) +Rails 3.2 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev). diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 19c690233c..67f4a3c02c 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 4.0 Release Notes =============================== @@ -8,7 +10,10 @@ Highlights in Rails 4.0: * Turbolinks * Russian Doll Caching -These release notes cover only the major changes. To know 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/master) in the main Rails repository on GitHub. +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/4-0-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- @@ -226,11 +231,11 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default` * New method `reversible` makes it possible to specify code to be run when migrating up or down. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method) + See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/active_record_migrations.md#using-reversible) * New method `revert` will revert a whole migration or the given block. If migrating down, the given migration / block is run normally. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) + See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/active_record_migrations.md#reverting-previous-migrations) * Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 924e5d90db..8d5557be6e 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails 4.1 Release Notes =============================== @@ -8,10 +10,10 @@ Highlights in Rails 4.1: * Action Pack variants * Action Mailer previews -These release notes cover only the major changes. To know 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/master) in the main -Rails repository on GitHub. +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/4-1-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- @@ -64,7 +66,7 @@ Spring is running: ``` Have a look at the -[Spring README](https://github.com/jonleighton/spring/blob/master/README.md) to +[Spring README](https://github.com/rails/spring/blob/master/README.md) to see all available features. See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#spring) @@ -136,7 +138,7 @@ end ### Action Mailer Previews -Action Mailer previews provide a way to visually see how emails look by visiting +Action Mailer previews provide a way to see how emails look by visiting a special URL that renders them. You implement a preview class whose methods return the mail object you'd like @@ -157,7 +159,7 @@ By default, these preview classes live in `test/mailers/previews`. This can be configured using the `preview_path` option. See its -[documentation](http://api.rubyonrails.org/v4.1.0/classes/ActionMailer/Base.html) +[documentation](http://api.rubyonrails.org/v4.1.0/classes/ActionMailer/Base.html#class-ActionMailer::Base-label-Previewing+emails) for a detailed write up. ### Active Record enums @@ -175,6 +177,8 @@ conversation.active? # => false conversation.status # => "archived" Conversation.archived # => Relation for all archived Conversations + +Conversation.statuses # => { "active" => 0, "archived" => 1 } ``` See its @@ -241,6 +245,7 @@ unless they use `xhr`. Upgrade your tests to be explicit about expecting XmlHttpRequests. Instead of `post :create, format: :js`, switch to the explicit `xhr :post, :create, format: :js`. + Railties -------- @@ -267,7 +272,7 @@ for detailed changes. ### Notable changes * The [Spring application - preloader](https://github.com/jonleighton/spring) is now installed + 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 production. ([Pull Request](https://github.com/rails/rails/pull/12958)) @@ -278,7 +283,7 @@ for detailed changes. * Exposed `MiddlewareStack#unshift` to environment configuration. ([Pull Request](https://github.com/rails/rails/pull/12479)) -* Add `Application#message_verifier` method to return a message +* Added `Application#message_verifier` method to return a message verifier. ([Pull Request](https://github.com/rails/rails/pull/12995)) * The `test_help.rb` file which is required by the default generated test @@ -288,6 +293,11 @@ for detailed changes. with `config.active_record.maintain_test_schema = false`. ([Pull Request](https://github.com/rails/rails/pull/13528)) +* Introduce `Rails.gem_version` as a convenience method to return + `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform + version comparison. ([Pull Request](https://github.com/rails/rails/pull/14103)) + + Action Pack ----------- @@ -335,6 +345,24 @@ for detailed changes. * Separated Action View completely from Action Pack. ([Pull Request](https://github.com/rails/rails/pull/11032)) +* Log which keys were affected by deep + munge. ([Pull Request](https://github.com/rails/rails/pull/13813)) + +* New config option `config.action_dispatch.perform_deep_munge` to opt out of + params "deep munging" that was used to address security vulnerability + CVE-2013-0155. ([Pull Request](https://github.com/rails/rails/pull/13188)) + +* New config option `config.action_dispatch.cookies_serializer` for specifying a + serializer for the signed and encrypted cookie jars. (Pull Requests + [1](https://github.com/rails/rails/pull/13692), + [2](https://github.com/rails/rails/pull/13945) / + [More Details](upgrading_ruby_on_rails.html#cookies-serializer)) + +* Added `render :plain`, `render :html` and `render + :body`. ([Pull Request](https://github.com/rails/rails/pull/14062) / + [More Details](upgrading_ruby_on_rails.html#rendering-content-from-string)) + + Action Mailer ------------- @@ -344,9 +372,13 @@ for detailed changes. ### Notable changes +* Added mailer previews feature based on 37 Signals mail_view + gem. ([Commit](https://github.com/rails/rails/commit/d6dec7fcb6b8fddf8c170182d4fe64ecfc7b2261)) + * Instrument the generation of Action Mailer messages. The time it takes to generate a message is written to the log. ([Pull Request](https://github.com/rails/rails/pull/12556)) + Active Record ------------- @@ -366,7 +398,7 @@ for detailed changes. * Removed deprecated `scope` use without passing a callable object. * Removed deprecated `transaction_joinable=` in favor of `begin_transaction` - with `d:joinable` option. + with a `:joinable` option. * Removed deprecated `decrement_open_transactions`. @@ -411,6 +443,8 @@ for detailed changes. * Remove implicit join references that were deprecated in 4.0. * Removed `activerecord-deprecated_finders` as a dependency. + Please see [the gem README](https://github.com/rails/activerecord-deprecated_finders#active-record-deprecated-finders) + for more info. * Removed usage of `implicit_readonly`. Please use `readonly` method explicitly to mark records as @@ -420,11 +454,6 @@ for detailed changes. * Deprecated `quoted_locking_column` method, which isn't used anywhere. -* Deprecated the delegation of Array bang methods for associations. - To use them, instead first call `#to_a` on the association to access the - array to be acted - on. ([Pull Request](https://github.com/rails/rails/pull/12129)) - * Deprecated `ConnectionAdapters::SchemaStatements#distinct`, as it is no longer used by internals. ([Pull Request](https://github.com/rails/rails/pull/10556)) @@ -438,6 +467,12 @@ for detailed changes. ### Notable changes +* Default scopes are no longer overridden by chained conditions. + + Before this change when you defined a `default_scope` in a model + it was overridden by chained conditions in the same field. Now it + is merged like any other scope. [More Details](upgrading_ruby_on_rails.html#changes-on-default-scopes). + * Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from a model's attribute or method. ([Pull Request](https://github.com/rails/rails/pull/12891)) @@ -498,7 +533,35 @@ for detailed changes. object. Helper methods used by multiple fixtures should be defined on modules included in `ActiveRecord::FixtureSet.context_class`. ([Pull Request](https://github.com/rails/rails/pull/13022)) -* Don't create or drop the test database if RAILS_ENV is specified explicitly. +* Don't create or drop the test database if RAILS_ENV is specified + explicitly. ([Pull Request](https://github.com/rails/rails/pull/13629)) + +* `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert + to an `Array` by calling `#to_a` before using these methods. ([Pull Request](https://github.com/rails/rails/pull/13314)) + +* `find_in_batches`, `find_each`, `Result#each` and `Enumerable#index_by` now + return an `Enumerator` that can calculate its + size. ([Pull Request](https://github.com/rails/rails/pull/13938)) + +* `scope`, `enum` and Associations now raise on "dangerous" name + conflicts. ([Pull Request](https://github.com/rails/rails/pull/13450), + [Pull Request](https://github.com/rails/rails/pull/13896)) + +* `second` through `fifth` methods act like the `first` + finder. ([Pull Request](https://github.com/rails/rails/pull/13757)) + +* Make `touch` fire the `after_commit` and `after_rollback` + callbacks. ([Pull Request](https://github.com/rails/rails/pull/12031)) + +* Enable partial indexes for `sqlite >= 3.8.0`. + ([Pull Request](https://github.com/rails/rails/pull/13350)) + +* Make `change_column_null` + revertible. ([Commit](https://github.com/rails/rails/commit/724509a9d5322ff502aefa90dd282ba33a281a96)) + +* Added a flag to disable schema dump after migration. This is set to `false` + by default in the production environment for new applications. + ([Pull Request](https://github.com/rails/rails/pull/13948)) Active Model ------------ @@ -517,6 +580,13 @@ for detailed changes. * Added new API methods `reset_changes` and `changes_applied` to `ActiveModel::Dirty` that control changes state. +* Ability to specify multiple contexts when defining a + validation. ([Pull Request](https://github.com/rails/rails/pull/13754)) + +* `attribute_changed?` now accepts a hash to check if the attribute was changed + `:from` and/or `:to` a given + value. ([Pull Request](https://github.com/rails/rails/pull/13131)) + Active Support -------------- @@ -567,6 +637,12 @@ for detailed changes. * Removed deprecated `assert_present` and `assert_blank` methods, use `assert object.blank?` and `assert object.present?` instead. +* Remove deprecated `#filter` method for filter objects, use the corresponding + method instead (e.g. `#before` for a before filter). + +* Removed 'cow' => 'kine' irregular inflection from default + inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9)) + ### Deprecations * Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to @@ -583,11 +659,14 @@ for detailed changes. [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) * Deprecated `ActiveSupport.encode_big_decimal_as_string` option. This feature has - been extracetd into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) + been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) gem. ([Pull Request](https://github.com/rails/rails/pull/13060) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) +* Deprecate custom `BigDecimal` + serialization. ([Pull Request](https://github.com/rails/rails/pull/13911)) + ### Notable changes * `ActiveSupport`'s JSON encoder has been rewritten to take advantage of the @@ -600,9 +679,12 @@ for detailed changes. [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) * Added `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These - methods change current time to the given time or time difference by stubbing - `Time.now` and - `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824)) + methods change current time to the given time or duration by stubbing + `Time.now` and `Date.today`. + +* Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method returns + the current time to the original state, by removing the stubs added by `travel` + and `travel_to`. ([Pull Request](https://github.com/rails/rails/pull/13884)) * Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed them to JavaScript functions like @@ -613,11 +695,33 @@ for detailed changes. `at_middle_of_day` as aliases. ([Pull Request](https://github.com/rails/rails/pull/10879)) +* Added `Date#all_week/month/quarter/year` for generating date + ranges. ([Pull Request](https://github.com/rails/rails/pull/9685)) + +* Added `Time.zone.yesterday` and + `Time.zone.tomorrow`. ([Pull Request](https://github.com/rails/rails/pull/12822)) + * Added `String#remove(pattern)` as a short-hand for the common pattern of `String#gsub(pattern,'')`. ([Commit](https://github.com/rails/rails/commit/5da23a3f921f0a4a3139495d2779ab0d3bd4cb5f)) -* Removed 'cow' => 'kine' irregular inflection from default - inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9)) +* Added `Hash#compact` and `Hash#compact!` for removing items with nil value + from hash. ([Pull Request](https://github.com/rails/rails/pull/13632)) + +* `blank?` and `present?` commit to return + singletons. ([Commit](https://github.com/rails/rails/commit/126dc47665c65cd129967cbd8a5926dddd0aa514)) + +* Default the new `I18n.enforce_available_locales` config to `true`, meaning + `I18n` will make sure that all locales passed to it must be declared in the + `available_locales` + list. ([Pull Request](https://github.com/rails/rails/pull/13341)) + +* Introduce `Module#concerning`: a natural, low-ceremony way to separate + responsibilities within a + class. ([Commit](https://github.com/rails/rails/commit/1eee0ca6de975b42524105a59e0521d18b38ab81)) + +* Added `Object#presence_in` to simplify value whitelisting. + ([Commit](https://github.com/rails/rails/commit/4edca106daacc5a159289eae255207d160f22396)) + Credits ------- diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md new file mode 100644 index 0000000000..366d9d26b4 --- /dev/null +++ b/guides/source/4_2_release_notes.md @@ -0,0 +1,855 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Ruby on Rails 4.2 Release Notes +=============================== + +Highlights in Rails 4.2: + +* Active Job +* Asynchronous mails +* Adequate Record +* Web Console +* Foreign key support + +These release notes cover only the major changes. To learn about other +features, bug fixes, and changes, please refer to the changelogs or check out +the [list of commits](https://github.com/rails/rails/commits/4-2-stable) in +the main Rails repository on GitHub. + +-------------------------------------------------------------------------------- + +Upgrading to Rails 4.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 4.1 in case you +haven't and make sure your application still runs as expected before attempting +to upgrade to Rails 4.2. A list of things to watch out for when upgrading is +available in the guide [Upgrading Ruby on +Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2). + + +Major Features +-------------- + +### Active Job + +Active Job is a new framework in Rails 4.2. It is a common interface on top of +queuing systems like [Resque](https://github.com/resque/resque), [Delayed +Job](https://github.com/collectiveidea/delayed_job), +[Sidekiq](https://github.com/mperham/sidekiq), and more. + +Jobs written with the Active Job API run on any of the supported queues thanks +to their respective adapters. Active Job comes pre-configured with an inline +runner that executes jobs right away. + +Jobs often need to take Active Record objects as arguments. Active Job passes +object references as URIs (uniform resource identifiers) instead of marshaling +the object itself. The new [Global ID](https://github.com/rails/globalid) +library builds URIs and looks up the objects they reference. Passing Active +Record objects as job arguments just works by using Global ID internally. + +For example, if `trashable` is an Active Record object, then this job runs +just fine with no serialization involved: + +```ruby +class TrashableCleanupJob < ActiveJob::Base + def perform(trashable, depth) + trashable.cleanup(depth) + end +end +``` + +See the [Active Job Basics](active_job_basics.html) guide for more +information. + +### Asynchronous Mails + +Building on top of Active Job, Action Mailer now comes with a `deliver_later` +method that sends emails via the queue, so it doesn't block the controller or +model if the queue is asynchronous (the default inline queue blocks). + +Sending emails right away is still possible with `deliver_now`. + +### Adequate Record + +Adequate Record is a set of performance improvements in Active Record that makes +common `find` and `find_by` calls and some association queries up to 2x faster. + +It works by caching common SQL queries as prepared statements and reusing them +on similar calls, skipping most of the query-generation work on subsequent +calls. For more details, please refer to [Aaron Patterson's blog +post](http://tenderlovemaking.com/2014/02/19/adequaterecord-pro-like-activerecord.html). + +Active Record will automatically take advantage of this feature on +supported operations without any user involvement or code changes. Here are +some examples of supported operations: + +```ruby +Post.find(1) # First call generates and cache the prepared statement +Post.find(2) # Subsequent calls reuse the cached prepared statement + +Post.find_by_title('first post') +Post.find_by_title('second post') + +Post.find_by(title: 'first post') +Post.find_by(title: 'second post') + +post.comments +post.comments(true) +``` + +It's important to highlight that, as the examples above suggest, the prepared +statements do not cache the values passed in the method calls; rather, they +have placeholders for them. + +Caching is not used in the following scenarios: + +- The model has a default scope +- The model uses single table inheritance +- `find` with a list of ids, e.g.: + + ```ruby + # not cached + Post.find(1, 2, 3) + Post.find([1,2]) + ``` + +- `find_by` with SQL fragments: + + ```ruby + Post.find_by('published_at < ?', 2.weeks.ago) + ``` + +### Web Console + +New applications generated with Rails 4.2 now come with the [Web +Console](https://github.com/rails/web-console) gem by default. Web Console adds +an interactive Ruby console on every error page and provides a `console` view +and controller helpers. + +The interactive console on error pages lets you execute code in the context of +the place where the exception originated. The `console` helper, if called +anywhere in a view or controller, launches an interactive console with the final +context, once rendering has completed. + +### Foreign Key Support + +The migration DSL now supports adding and removing foreign keys. They are dumped +to `schema.rb` as well. At this time, only the `mysql`, `mysql2` and `postgresql` +adapters support foreign keys. + +```ruby +# add a foreign key to `articles.author_id` referencing `authors.id` +add_foreign_key :articles, :authors + +# add a foreign key to `articles.author_id` referencing `users.lng_id` +add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id" + +# remove the foreign key on `accounts.branch_id` +remove_foreign_key :accounts, :branches + +# remove the foreign key on `accounts.owner_id` +remove_foreign_key :accounts, column: :owner_id +``` + +See the API documentation on +[add_foreign_key](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key) +and +[remove_foreign_key](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_foreign_key) +for a full description. + + +Incompatibilities +----------------- + +Previously deprecated functionality has been removed. Please refer to the +individual components for new deprecations in this release. + +The following changes may require immediate action upon upgrade. + +### `render` with a String Argument + +Previously, calling `render "foo/bar"` in a controller action was equivalent to +`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean +`render template: "foo/bar"` instead. If you need to render a file, please +change your code to use the explicit form (`render file: "foo/bar"`) instead. + +### `respond_with` / Class-Level `respond_to` + +`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: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + respond_to :html, :json + + def show + @user = User.find(params[:id]) + respond_with @user + end +end +``` + +Instance-level `respond_to` is unaffected: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + def show + @user = User.find(params[:id]) + respond_to do |format| + format.html + format.json { render json: @user } + end + end +end +``` + +### Default Host for `rails server` + +Due to a [change in Rack](https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc), +`rails server` now listens on `localhost` instead of `0.0.0.0` by default. This +should have minimal impact on the standard development workflow as both +http://127.0.0.1:3000 and http://localhost:3000 will continue to work as before +on your own machine. + +However, with this change you will no longer be able to access the Rails +server from a different machine, for example if your development environment +is in a virtual machine and you would like to access it from the host machine. +In such cases, please start the server with `rails server -b 0.0.0.0` to +restore the old behavior. + +If you do this, be sure to configure your firewall properly such that only +trusted machines on your network can access your development server. + +### HTML Sanitizer + +The HTML sanitizer has been replaced with a new, more robust, implementation +built upon [Loofah](https://github.com/flavorjones/loofah) and +[Nokogiri](https://github.com/sparklemotion/nokogiri). The new sanitizer is +more secure and its sanitization is more powerful and flexible. + +Due to the new algorithm, the sanitized output may be different for certain +pathological inputs. + +If you have a particular need for the exact output of the old sanitizer, you +can add the [rails-deprecated_sanitizer](https://github.com/kaspth/rails-deprecated_sanitizer) +gem to the `Gemfile`, to have the old behavior. The gem does not issue +deprecation warnings because it is opt-in. + +`rails-deprecated_sanitizer` will be supported for Rails 4.2 only; it will not +be maintained for Rails 5.0. + +See [this blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/) +for more details on the changes in the new sanitizer. + +### `assert_select` + +`assert_select` is now based on [Nokogiri](https://github.com/sparklemotion/nokogiri). +As a result, some previously-valid selectors are now unsupported. If your +application is using any of these spellings, you will need to update them: + +* Values in attribute selectors may need to be quoted if they contain + non-alphanumeric characters. + + ``` + # before + a[href=/] + a[href$=/] + + # now + a[href="/"] + a[href$="/"] + ``` + +* DOMs built from HTML source containing invalid HTML with improperly + nested elements may differ. + + For example: + + ``` ruby + # content: <div><i><p></i></div> + + # before: + assert_select('div > i') # => true + assert_select('div > p') # => false + assert_select('i > p') # => true + + # now: + assert_select('div > i') # => true + assert_select('div > p') # => true + assert_select('i > p') # => false + ``` + +* If the data selected contains entities, the value selected for comparison + used to be raw (e.g. `AT&T`), and now is evaluated + (e.g. `AT&T`). + + ``` ruby + # content: <p>AT&T</p> + + # before: + assert_select('p', 'AT&T') # => true + assert_select('p', 'AT&T') # => false + + # now: + assert_select('p', 'AT&T') # => true + assert_select('p', 'AT&T') # => false + ``` + + +Railties +-------- + +Please refer to the [Changelog][railties] for detailed changes. + +### Removals + +* The `--skip-action-view` option has been removed from the + app generator. ([Pull Request](https://github.com/rails/rails/pull/17042)) + +* The `rails application` command has been removed without replacement. + ([Pull Request](https://github.com/rails/rails/pull/11616)) + +### Deprecations + +* Deprecated missing `config.log_level` for production environments. + ([Pull Request](https://github.com/rails/rails/pull/16622)) + +* Deprecated `rake test:all` in favor of `rake test` as it now run all tests + in the `test` folder. + ([Pull Request](https://github.com/rails/rails/pull/17348)) + +* Deprecated `rake test:all:db` in favor of `rake test:db`. + ([Pull Request](https://github.com/rails/rails/pull/17348)) + +* Deprecated `Rails::Rack::LogTailer` without replacement. + ([Commit](https://github.com/rails/rails/commit/84a13e019e93efaa8994b3f8303d635a7702dbce)) + +### Notable changes + +* 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. + ([Pull Request](https://github.com/rails/rails/pull/16062)) + +* Introduced the `x` namespace for defining custom configuration options: + + ```ruby + # config/environments/production.rb + config.x.payment_processing.schedule = :daily + config.x.payment_processing.retries = 3 + config.x.super_debugger = true + ``` + + These options are then available through the configuration object: + + ```ruby + Rails.configuration.x.payment_processing.schedule # => :daily + Rails.configuration.x.payment_processing.retries # => 3 + Rails.configuration.x.super_debugger # => true + ``` + + ([Commit](https://github.com/rails/rails/commit/611849772dd66c2e4d005dcfe153f7ce79a8a7db)) + +* Introduced `Rails::Application.config_for` to load a configuration for the + current environment. + + ```ruby + # config/exception_notification.yml: + production: + url: http://127.0.0.1:8080 + namespace: my_app_production + development: + url: http://localhost:3001 + namespace: my_app_development + + # config/production.rb + Rails.application.configure do + config.middleware.use ExceptionNotifier, config_for(:exception_notification) + end + ``` + + ([Pull Request](https://github.com/rails/rails/pull/16129)) + +* Introduced a `--skip-turbolinks` option in the app generator to not generate + turbolinks integration. + ([Commit](https://github.com/rails/rails/commit/bf17c8a531bc8059d50ad731398002a3e7162a7d)) + +* Introduced a `bin/setup` script as a convention for automated setup code when + bootstrapping an application. + ([Pull Request](https://github.com/rails/rails/pull/15189)) + +* Changed the default value for `config.assets.digest` to `true` in development. + ([Pull Request](https://github.com/rails/rails/pull/15155)) + +* Introduced an API to register new extensions for `rake notes`. + ([Pull Request](https://github.com/rails/rails/pull/14379)) + +* Introduced an `after_bundle` callback for use in Rails templates. + ([Pull Request](https://github.com/rails/rails/pull/16359)) + +* Introduced `Rails.gem_version` as a convenience method to return + `Gem::Version.new(Rails.version)`. + ([Pull Request](https://github.com/rails/rails/pull/14101)) + + +Action Pack +----------- + +Please refer to the [Changelog][action-pack] for detailed changes. + +### Removals + +* `respond_with` and the class-level `respond_to` have been removed from Rails and + moved to the `responders` gem (version 2.0). Add `gem 'responders', '~> 2.0'` + to your `Gemfile` to continue using these features. + ([Pull Request](https://github.com/rails/rails/pull/16526), + [More Details](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#responders)) + +* Removed deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError` + in favor of `AbstractController::Helpers::MissingHelperError`. + ([Commit](https://github.com/rails/rails/commit/a1ddde15ae0d612ff2973de9cf768ed701b594e8)) + +### Deprecations + +* Deprecated the `only_path` option on `*_path` helpers. + ([Commit](https://github.com/rails/rails/commit/aa1fadd48fb40dd9396a383696134a259aa59db9)) + +* Deprecated `assert_tag`, `assert_no_tag`, `find_tag` and `find_all_tag` in + favor of `assert_select`. + ([Commit](https://github.com/rails/rails-dom-testing/commit/b12850bc5ff23ba4b599bf2770874dd4f11bf750)) + +* Deprecated support for setting the `:to` option of a router to a symbol or a + string that does not contain a "#" character: + + ```ruby + get '/posts', to: MyRackApp => (No change necessary) + get '/posts', to: 'post#index' => (No change necessary) + get '/posts', to: 'posts' => get '/posts', controller: :posts + get '/posts', to: :index => get '/posts', action: :index + ``` + + ([Commit](https://github.com/rails/rails/commit/cc26b6b7bccf0eea2e2c1a9ebdcc9d30ca7390d9)) + +* Deprecated support for string keys in URL helpers: + + ```ruby + # bad + root_path('controller' => 'posts', 'action' => 'index') + + # good + root_path(controller: 'posts', action: 'index') + ``` + + ([Pull Request](https://github.com/rails/rails/pull/17743)) + +### Notable changes + +* The `*_filter` family of methods have been removed from the documentation. Their + usage is discouraged in favor of the `*_action` family of methods: + + ``` + after_filter => after_action + append_after_filter => append_after_action + append_around_filter => append_around_action + append_before_filter => append_before_action + around_filter => around_action + before_filter => before_action + prepend_after_filter => prepend_after_action + prepend_around_filter => prepend_around_action + prepend_before_filter => prepend_before_action + skip_after_filter => skip_after_action + skip_around_filter => skip_around_action + skip_before_filter => skip_before_action + skip_filter => skip_action_callback + ``` + + If your application currently depends on these methods, you should use the + replacement `*_action` methods instead. These methods will be deprecated in + the future and will eventually be removed from Rails. + + (Commit [1](https://github.com/rails/rails/commit/6c5f43bab8206747a8591435b2aa0ff7051ad3de), + [2](https://github.com/rails/rails/commit/489a8f2a44dc9cea09154ee1ee2557d1f037c7d4)) + +* `render nothing: true` or rendering a `nil` body no longer add a single + space padding to the response body. + ([Pull Request](https://github.com/rails/rails/pull/14883)) + +* Rails now automatically includes the template's digest in ETags. + ([Pull Request](https://github.com/rails/rails/pull/16527)) + +* Segments that are passed into URL helpers are now automatically escaped. + ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f)) + +* Introduced the `always_permitted_parameters` option to configure which + parameters are permitted globally. The default value of this configuration + is `['controller', 'action']`. + ([Pull Request](https://github.com/rails/rails/pull/15933)) + +* Added the HTTP method `MKCALENDAR` from [RFC 4791](https://tools.ietf.org/html/rfc4791). + ([Pull Request](https://github.com/rails/rails/pull/15121)) + +* `*_fragment.action_controller` notifications now include the controller + and action name in the payload. + ([Pull Request](https://github.com/rails/rails/pull/14137)) + +* Improved the Routing Error page with fuzzy matching for route search. + ([Pull Request](https://github.com/rails/rails/pull/14619)) + +* Added an option to disable logging of CSRF failures. + ([Pull Request](https://github.com/rails/rails/pull/14280)) + +* When the Rails server is set to serve static assets, gzip assets will now be + served if the client supports it and a pre-generated gzip file (`.gz`) is on disk. + By default the asset pipeline generates `.gz` files for all compressible assets. + Serving gzip files minimizes data transfer and speeds up asset requests. Always + [use a CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) if you are + serving assets from your Rails server in production. + ([Pull Request](https://github.com/rails/rails/pull/16466)) + +* When calling the `process` helpers in an integration test the path needs to have + a leading slash. Previously you could omit it but that was a byproduct of the + implementation and not an intentional feature, e.g.: + + ```ruby + test "list all posts" do + get "/posts" + assert_response :success + end + ``` + +Action View +----------- + +Please refer to the [Changelog][action-view] for detailed changes. + +### Deprecations + +* Deprecated `AbstractController::Base.parent_prefixes`. + Override `AbstractController::Base.local_prefixes` when you want to change + where to find views. + ([Pull Request](https://github.com/rails/rails/pull/15026)) + +* Deprecated `ActionView::Digestor#digest(name, format, finder, options = {})`. + Arguments should be passed as a hash instead. + ([Pull Request](https://github.com/rails/rails/pull/14243)) + +### Notable changes + +* `render "foo/bar"` now expands to `render template: "foo/bar"` instead of + `render file: "foo/bar"`. + ([Pull Request](https://github.com/rails/rails/pull/16888)) + +* The form helpers no longer generate a `<div>` element with inline CSS around + the hidden fields. + ([Pull Request](https://github.com/rails/rails/pull/14738)) + +* Introduced a `#{partial_name}_iteration` special local variable for use with + partials that are rendered with a collection. It provides access to the + current state of the iteration via the `index`, `size`, `first?` and + `last?` methods. + ([Pull Request](https://github.com/rails/rails/pull/7698)) + +* Placeholder I18n follows the same convention as `label` I18n. + ([Pull Request](https://github.com/rails/rails/pull/16438)) + + +Action Mailer +------------- + +Please refer to the [Changelog][action-mailer] for detailed changes. + +### Deprecations + +* Deprecated `*_path` helpers in mailers. Always use `*_url` helpers instead. + ([Pull Request](https://github.com/rails/rails/pull/15840)) + +* Deprecated `deliver` / `deliver!` in favor of `deliver_now` / `deliver_now!`. + ([Pull Request](https://github.com/rails/rails/pull/16582)) + +### Notable changes + +* `link_to` and `url_for` generate absolute URLs by default in templates, + it is no longer needed to pass `only_path: false`. + ([Commit](https://github.com/rails/rails/commit/9685080a7677abfa5d288a81c3e078368c6bb67c)) + +* Introduced `deliver_later` which enqueues a job on the application's queue + to deliver emails asynchronously. + ([Pull Request](https://github.com/rails/rails/pull/16485)) + +* Added the `show_previews` configuration option for enabling mailer previews + outside of the development environment. + ([Pull Request](https://github.com/rails/rails/pull/15970)) + + +Active Record +------------- + +Please refer to the [Changelog][active-record] for detailed changes. + +### Removals + +* Removed `cache_attributes` and friends. All attributes are cached. + ([Pull Request](https://github.com/rails/rails/pull/15429)) + +* Removed deprecated method `ActiveRecord::Base.quoted_locking_column`. + ([Pull Request](https://github.com/rails/rails/pull/15612)) + +* Removed deprecated `ActiveRecord::Migrator.proper_table_name`. Use the + `proper_table_name` instance method on `ActiveRecord::Migration` instead. + ([Pull Request](https://github.com/rails/rails/pull/15512)) + +* Removed unused `:timestamp` type. Transparently alias it to `:datetime` + in all cases. Fixes inconsistencies when column types are sent outside of + Active Record, such as for XML serialization. + ([Pull Request](https://github.com/rails/rails/pull/15184)) + +### Deprecations + +* Deprecated swallowing of errors inside `after_commit` and `after_rollback`. + ([Pull Request](https://github.com/rails/rails/pull/16537)) + +* Deprecated broken support for automatic detection of counter caches on + `has_many :through` associations. You should instead manually specify the + counter cache on the `has_many` and `belongs_to` associations for the + through records. + ([Pull Request](https://github.com/rails/rails/pull/15754)) + +* Deprecated passing Active Record objects to `.find` or `.exists?`. Call + `id` on the objects first. + (Commit [1](https://github.com/rails/rails/commit/d92ae6ccca3bcfd73546d612efaea011270bd270), + [2](https://github.com/rails/rails/commit/d35f0033c7dec2b8d8b52058fb8db495d49596f7)) + +* Deprecated half-baked support for PostgreSQL range values with excluding + beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion + is not fully possible because Ruby ranges do not support excluded beginnings. + + The current solution of incrementing the beginning is not correct + and is now deprecated. For subtypes where we don't know how to increment + (e.g. `succ` is not defined) it will raise an `ArgumentError` for ranges + with excluding beginnings. + ([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3)) + +* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use + `DatabaseTasks.load_schema_current` instead. + ([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608)) + +* Deprecated `sanitize_sql_hash_for_conditions` without replacement. Using a + `Relation` for performing queries and updates is the preferred API. + ([Commit](https://github.com/rails/rails/commit/d5902c9e)) + +* Deprecated `add_timestamps` and `t.timestamps` without passing the `:null` + option. The default of `null: true` will change in Rails 5 to `null: false`. + ([Pull Request](https://github.com/rails/rails/pull/16481)) + +* Deprecated `Reflection#source_macro` without replacement as it is no longer + needed in Active Record. + ([Pull Request](https://github.com/rails/rails/pull/16373)) + +* Deprecated `serialized_attributes` without replacement. + ([Pull Request](https://github.com/rails/rails/pull/15704)) + +* Deprecated returning `nil` from `column_for_attribute` when no column + exists. It will return a null object in Rails 5.0. + ([Pull Request](https://github.com/rails/rails/pull/15878)) + +* Deprecated using `.joins`, `.preload` and `.eager_load` with associations + that depend on the instance state (i.e. those defined with a scope that + takes an argument) without replacement. + ([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1)) + +### Notable changes + +* `SchemaDumper` uses `force: :cascade` on `create_table`. This makes it + possible to reload a schema when foreign keys are in place. + +* Added a `:required` option to singular associations, which defines a + presence validation on the association. + ([Pull Request](https://github.com/rails/rails/pull/16056)) + +* `ActiveRecord::Dirty` now detects in-place changes to mutable values. + Serialized attributes on Active Record models are no longer saved when + unchanged. This also works with other types such as string columns and json + columns on PostgreSQL. + (Pull Requests [1](https://github.com/rails/rails/pull/15674), + [2](https://github.com/rails/rails/pull/15786), + [3](https://github.com/rails/rails/pull/15788)) + +* Introduced the `db:purge` Rake task to empty the database for the + current environment. + ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d)) + +* Introduced `ActiveRecord::Base#validate!` that raises + `ActiveRecord::RecordInvalid` if the record is invalid. + ([Pull Request](https://github.com/rails/rails/pull/8639)) + +* Introduced `validate` as an alias for `valid?`. + ([Pull Request](https://github.com/rails/rails/pull/14456)) + +* `touch` now accepts multiple attributes to be touched at once. + ([Pull Request](https://github.com/rails/rails/pull/14423)) + +* The PostgreSQL adapter now supports the `jsonb` datatype in PostgreSQL 9.4+. + ([Pull Request](https://github.com/rails/rails/pull/16220)) + +* The PostgreSQL and SQLite adapters no longer add a default limit of 255 + characters on string columns. + ([Pull Request](https://github.com/rails/rails/pull/14579)) + +* Added support for the `citext` column type in the PostgreSQL adapter. + ([Pull Request](https://github.com/rails/rails/pull/12523)) + +* Added support for user-created range types in the PostgreSQL adapter. + ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032)) + +* `sqlite3:///some/path` now resolves to the absolute system path + `/some/path`. For relative paths, use `sqlite3:some/path` instead. + (Previously, `sqlite3:///some/path` resolved to the relative path + `some/path`. This behavior was deprecated on Rails 4.1). + ([Pull Request](https://github.com/rails/rails/pull/14569)) + +* Added support for fractional seconds for MySQL 5.6 and above. + (Pull Request [1](https://github.com/rails/rails/pull/8240), + [2](https://github.com/rails/rails/pull/14359)) + +* Added `ActiveRecord::Base#pretty_print` to pretty print models. + ([Pull Request](https://github.com/rails/rails/pull/15172)) + +* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`, + meaning that it no longer retains the extra attributes from custom + `SELECT`s. + ([Pull Request](https://github.com/rails/rails/pull/15866)) + +* `ActiveRecord::Base#reflections` now returns a hash with string keys instead + of symbol keys. ([Pull Request](https://github.com/rails/rails/pull/17718)) + +* The `references` method in migrations now supports a `type` option for + specifying the type of the foreign key (e.g. `:uuid`). + ([Pull Request](https://github.com/rails/rails/pull/16231)) + +Active Model +------------ + +Please refer to the [Changelog][active-model] for detailed changes. + +### Removals + +* Removed deprecated `Validator#setup` without replacement. + ([Pull Request](https://github.com/rails/rails/pull/10716)) + +### Deprecations + +* Deprecated `reset_#{attribute}` in favor of `restore_#{attribute}`. + ([Pull Request](https://github.com/rails/rails/pull/16180)) + +* Deprecated `ActiveModel::Dirty#reset_changes` in favor of + `clear_changes_information`. + ([Pull Request](https://github.com/rails/rails/pull/16180)) + +### Notable changes + +* Introduced `validate` as an alias for `valid?`. + ([Pull Request](https://github.com/rails/rails/pull/14456)) + +* Introduced the `restore_attributes` method in `ActiveModel::Dirty` to restore + the changed (dirty) attributes to their previous values. + (Pull Request [1](https://github.com/rails/rails/pull/14861), + [2](https://github.com/rails/rails/pull/16180)) + +* `has_secure_password` no longer disallows blank passwords (i.e. passwords + that contains only spaces) by default. + ([Pull Request](https://github.com/rails/rails/pull/16412)) + +* `has_secure_password` now verifies that the given password is less than 72 + characters if validations are enabled. + ([Pull Request](https://github.com/rails/rails/pull/15708)) + +Active Support +-------------- + +Please refer to the [Changelog][active-support] for detailed changes. + +### Removals + +* Removed deprecated `Numeric#ago`, `Numeric#until`, `Numeric#since`, + `Numeric#from_now`. + ([Commit](https://github.com/rails/rails/commit/f1eddea1e3f6faf93581c43651348f48b2b7d8bb)) + +* Removed deprecated string based terminators for `ActiveSupport::Callbacks`. + ([Pull Request](https://github.com/rails/rails/pull/15100)) + +### Deprecations + +* Deprecated `Kernel#silence_stderr`, `Kernel#capture` and `Kernel#quietly` + without replacement. + ([Pull Request](https://github.com/rails/rails/pull/13392)) + +* Deprecated `Class#superclass_delegating_accessor`, use + `Class#class_attribute` instead. + ([Pull Request](https://github.com/rails/rails/pull/14271)) + +* Deprecated `ActiveSupport::SafeBuffer#prepend!` as + `ActiveSupport::SafeBuffer#prepend` now performs the same function. + ([Pull Request](https://github.com/rails/rails/pull/14529)) + +### Notable changes + +* Introduced a new configuration option `active_support.test_order` for + specifying the order test cases are executed. This option currently defaults + to `:sorted` but will be changed to `:random` in Rails 5.0. + ([Commit](https://github.com/rails/rails/commit/53e877f7d9291b2bf0b8c425f9e32ef35829f35b)) + +* `Object#try` and `Object#try!` can now be used without an explicit receiver in the block. + ([Commit](https://github.com/rails/rails/commit/5e51bdda59c9ba8e5faf86294e3e431bd45f1830), + [Pull Request](https://github.com/rails/rails/pull/17361)) + +* The `travel_to` test helper now truncates the `usec` component to 0. + ([Commit](https://github.com/rails/rails/commit/9f6e82ee4783e491c20f5244a613fdeb4024beb5)) + +* Introduced `Object#itself` as an identity function. + (Commit [1](https://github.com/rails/rails/commit/702ad710b57bef45b081ebf42e6fa70820fdd810), + [2](https://github.com/rails/rails/commit/64d91122222c11ad3918cc8e2e3ebc4b0a03448a)) + +* `Object#with_options` can now be used without an explicit receiver in the block. + ([Pull Request](https://github.com/rails/rails/pull/16339)) + +* Introduced `String#truncate_words` to truncate a string by a number of words. + ([Pull Request](https://github.com/rails/rails/pull/16190)) + +* Added `Hash#transform_values` and `Hash#transform_values!` to simplify a + common pattern where the values of a hash must change, but the keys are left + the same. + ([Pull Request](https://github.com/rails/rails/pull/15819)) + +* The `humanize` inflector helper now strips any leading underscores. + ([Commit](https://github.com/rails/rails/commit/daaa21bc7d20f2e4ff451637423a25ff2d5e75c7)) + +* Introduced `Concern#class_methods` as an alternative to + `module ClassMethods`, as well as `Kernel#concern` to avoid the + `module Foo; extend ActiveSupport::Concern; end` boilerplate. + ([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad)) + +* New [guide](constant_autoloading_and_reloading.html) about constant autoloading and reloading. + +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 today. Kudos to all of them. + +[railties]: https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md +[action-pack]: https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md +[action-view]: https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md +[action-mailer]: https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md +[active-record]: https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md +[active-model]: https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md +[active-support]: https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md diff --git a/guides/source/_license.html.erb b/guides/source/_license.html.erb index 00b4466f50..d22f016948 100644 --- a/guides/source/_license.html.erb +++ b/guides/source/_license.html.erb @@ -1,2 +1,2 @@ -<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p> +<p>This work is licensed under a <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International</a> License</p> <p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p> diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 27c53689c4..67f5f1cdd5 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -10,13 +10,15 @@ </p> <% else %> <p> - These are the new guides for Rails 4.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 5.0 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 Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.16/">http://guides.rubyonrails.org/v3.2.16/</a>. -</p> -<p> - The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>. +The guides for earlier releases: +<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>, +<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>, +<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>, +<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and +<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>. </p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index f394daa6aa..80000baf66 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Action Controller Overview ========================== @@ -34,7 +36,7 @@ The naming convention of controllers in Rails favors pluralization of the last w Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details. -NOTE: The controller naming convention differs from the naming convention of models, which expected to be named in singular form. +NOTE: The controller naming convention differs from the naming convention of models, which are expected to be named in singular form. Methods and Actions @@ -112,6 +114,10 @@ NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&id The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type. +NOTE: Values such as `[nil]` or `[nil, nil, ...]` in `params` are replaced +with `[]` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation) +for more information. + To send a hash you include the key name inside the brackets: ```html @@ -256,7 +262,7 @@ used: params.require(:log_entry).permit! ``` -This will mark the `:log_entry` parameters hash and any subhash of it +This will mark the `:log_entry` parameters hash and any sub-hash of it permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned. @@ -360,33 +366,48 @@ If you need a different session storage mechanism, you can change it in the `con # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rails g active_record:session_migration") -# YourApp::Application.config.session_store :active_record_store +# Rails.application.config.session_store :active_record_store ``` Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`: ```ruby # Be sure to restart your server when you modify this file. -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session' +Rails.application.config.session_store :cookie_store, key: '_your_app_session' ``` You can also pass a `:domain` key and specify the domain name for the cookie: ```ruby # Be sure to restart your server when you modify this file. -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" +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/initializers/secret_token.rb` +Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml` ```ruby # Be sure to restart your server when you modify this file. -# Your secret key for verifying the integrity of signed cookies. +# 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. -YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...' +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +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"] %> ``` NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions. @@ -568,6 +589,62 @@ end Note that while for session values you set the key to `nil`, to delete a cookie value you should use `cookies.delete(:key)`. +Rails also provides a signed cookie jar and an encrypted cookie jar for storing +sensitive data. The signed cookie jar appends a cryptographic signature on the +cookie values to protect their integrity. The encrypted cookie jar encrypts the +values in addition to signing them, so that they cannot be read by the end user. +Refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) +for more details. + +These special cookie jars use a serializer to serialize the assigned values into +strings and deserializes them into Ruby objects on read. + +You can specify what serializer to use: + +```ruby +Rails.application.config.action_dispatch.cookies_serializer = :json +``` + +The default serializer for new applications is `:json`. For compatibility with +old applications with existing cookies, `:marshal` is used when `serializer` +option is not specified. + +You may also set this option to `:hybrid`, in which case Rails would transparently +deserialize existing (`Marshal`-serialized) cookies on read and re-write them in +the `JSON` format. This is useful for migrating existing applications to the +`:json` serializer. + +It is also possible to pass a custom serializer that responds to `load` and +`dump`: + +```ruby +Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer +``` + +When using the `:json` or `:hybrid` serializer, you should beware that not all +Ruby objects can be serialized as JSON. For example, `Date` and `Time` objects +will be serialized as strings, and `Hash`es will have their keys stringified. + +```ruby +class CookiesController < ApplicationController + def set_cookie + cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014 + redirect_to action: 'read_cookie' + end + + def read_cookie + cookies.encrypted[:expiration_date] # => "2014-03-20" + end +end +``` + +It's advisable that you only store simple data (strings and numbers) in cookies. +If you have to store complex objects, you would need to handle the conversion +manually when reading the values on subsequent requests. + +If you use the cookie session store, this would apply to the `session` and +`flash` hash as well. + Rendering XML and JSON data --------------------------- @@ -683,7 +760,7 @@ class ApplicationController < ActionController::Base end class LoginFilter - def self.filter(controller) + def self.before(controller) unless controller.send(:logged_in?) controller.flash[:error] = "You must be logged in to access this section" controller.redirect_to controller.new_login_url @@ -692,7 +769,7 @@ class LoginFilter end ``` -Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method `filter` which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same `filter` method, which will get run in the same way. The method must `yield` to execute the action. Alternatively, it can have both a `before` and an `after` method that are run before and after the action. +Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class must implement a method with the same name as the filter, so for the `before_action` filter the class must implement a `before` method, and so on. The `around` method must `yield` to execute the action. Request Forgery Protection -------------------------- @@ -917,6 +994,11 @@ you would like in a response object. The `ActionController::Live` module allows you to create a persistent connection with a browser. Using this module, you will be able to send arbitrary data to the browser at specific points in time. +NOTE: The default Rails server (WEBrick) is a buffering web server and does not +support streaming. In order to use this feature, you'll need to use a non buffering +server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org) +or [Passenger](https://www.phusionpassenger.com). + #### Incorporating Live Streaming Including `ActionController::Live` inside of your controller class will provide @@ -1003,7 +1085,7 @@ Rails keeps a log file for each environment in the `log` folder. These are extre ### Parameters Filtering -You can filter certain request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log. +You can filter out sensitive request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log. ```ruby config.filter_parameters << :password @@ -1011,7 +1093,7 @@ config.filter_parameters << :password ### Redirects Filtering -Sometimes it's desirable to filter out from log files some sensible locations your application is redirecting to. +Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to. You can do that by using the `config.filter_redirect` configuration option: ```ruby @@ -1052,7 +1134,7 @@ class ApplicationController < ActionController::Base private def record_not_found - render text: "404 Not Found", status: 404 + render plain: "404 Not Found", status: 404 end end ``` @@ -1089,6 +1171,8 @@ 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). + NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information. Force HTTPS protocol diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 61fd762304..e55ff16495 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Action Mailer Basics ==================== @@ -17,7 +19,10 @@ After reading this guide, you will know: Introduction ------------ -Action Mailer allows you to send emails from your application using mailer classes and views. Mailers work very similarly to controllers. They inherit from `ActionMailer::Base` and live in `app/mailers`, and they have associated views that appear in `app/views`. +Action Mailer allows you to send emails from your application using mailer classes +and views. Mailers work very similarly to controllers. They inherit from +`ActionMailer::Base` and live in `app/mailers`, and they have associated views +that appear in `app/views`. Sending Emails -------------- @@ -30,12 +35,28 @@ views. #### Create the Mailer ```bash -$ rails generate mailer UserMailer +$ bin/rails generate mailer UserMailer create app/mailers/user_mailer.rb +create app/mailers/application_mailer.rb invoke erb create app/views/user_mailer +create app/views/layouts/mailer.text.erb +create app/views/layouts/mailer.html.erb invoke test_unit create test/mailers/user_mailer_test.rb +create test/mailers/previews/user_mailer_preview.rb +``` + +```ruby +# app/mailers/application_mailer.rb +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout 'mailer' +end + +# app/mailers/user_mailer.rb +class UserMailer < ApplicationMailer +end ``` As you can see, you can generate mailers just like you use other generators with @@ -60,8 +81,7 @@ delivered via email. `app/mailers/user_mailer.rb` contains an empty mailer: ```ruby -class UserMailer < ActionMailer::Base - default from: 'from@example.com' +class UserMailer < ApplicationMailer end ``` @@ -69,7 +89,7 @@ Let's add a method called `welcome_email`, that will send an email to the user's registered email address: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -84,8 +104,11 @@ Here is a quick explanation of the items presented in the preceding method. For a full list of all available options, please have a look further down at the Complete List of Action Mailer user-settable attributes section. -* `default Hash` - This is a hash of default values for any email you send from this mailer. In this case we are setting the `:from` header to a value for all messages in this class. This can be overridden on a per-email basis. -* `mail` - The actual email message, we are passing the `:to` and `:subject` headers in. +* `default Hash` - This is a hash of default values for any email you send from +this mailer. In this case we are setting the `:from` header to a value for all +messages in this class. This can be overridden on a per-email basis. +* `mail` - The actual email message, we are passing the `:to` and `:subject` +headers in. Just like controllers, any instance variables we define in the method become available for use in the views. @@ -138,7 +161,7 @@ When you call the `mail` method now, Action Mailer will detect the two templates Mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the -Email protocols instead. Due to this, it makes sense to just have your +email protocols instead. Due to this, it makes sense to just have your controller tell the Mailer to send an email when a user is successfully created. Setting this up is painfully simple. @@ -146,14 +169,17 @@ Setting this up is painfully simple. First, let's create a simple `User` scaffold: ```bash -$ rails generate scaffold user name email login -$ rake db:migrate +$ bin/rails generate scaffold user name email login +$ bin/rake 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 +`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.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: ```ruby class UsersController < ApplicationController @@ -164,8 +190,8 @@ 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 + # Tell the UserMailer to send a welcome email after save + UserMailer.welcome_email(@user).deliver_later format.html { redirect_to(@user, notice: 'User was successfully created.') } format.json { render json: @user, status: :created, location: @user } @@ -178,8 +204,29 @@ class UsersController < ApplicationController end ``` -The method `welcome_email` returns a `Mail::Message` object which can then just -be told `deliver` to send itself out. +NOTE: Active Job's default behavior is to execute jobs ':inline'. So, you can use +`deliver_later` now to send emails, and when you later decide to start sending +them from a background job, you'll only need to set up Active Job to use a queueing +backend (Sidekiq, Resque, etc). + +If you want to send emails right away (from a cronjob for example) just call +`deliver_now`: + +```ruby +class SendWeeklySummary + def run + User.find_each do |user| + UserMailer.weekly_summary(user).deliver_now + end + end +end +``` + +The method `welcome_email` returns a `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 +you want to inspect, alter or do anything else with the `Mail::Message` object you can +access it with the `message` method on the `ActionMailer::MessageDelivery` object. ### Auto encoding header values @@ -230,9 +277,11 @@ different, encode your content and pass in the encoded content and encoding in a ```ruby encoded_content = SpecialEncode(File.read('/path/to/filename.jpg')) - attachments['filename.jpg'] = {mime_type: 'application/x-gzip', - encoding: 'SpecialEncoding', - content: encoded_content } + attachments['filename.jpg'] = { + mime_type: 'application/x-gzip', + encoding: 'SpecialEncoding', + content: encoded_content + } ``` NOTE: If you specify an encoding, Mail will assume that your content is already @@ -266,8 +315,7 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p ```html+erb <p>Hello there, this is our image</p> - <%= image_tag attachments['image.jpg'].url, alt: 'My Photo', - class: 'photos' %> + <%= image_tag attachments['image.jpg'].url, alt: 'My Photo', class: 'photos' %> ``` #### Sending Email To Multiple Recipients @@ -301,7 +349,7 @@ email address in the format `"Full Name <email>"`. ```ruby def welcome_email(user) @user = user - email_with_name = "#{@user.name} <#{@user.email}>" + email_with_name = %("#{@user.name}" <#{@user.email}>) mail(to: email_with_name, subject: 'Welcome to My Awesome Site') end ``` @@ -317,7 +365,7 @@ for the HTML version and `welcome_email.text.erb` for the plain text version. To change the default mailer view for your action you do something like: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -339,7 +387,7 @@ If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -369,7 +417,7 @@ layout. In order to use a different file, call `layout` in your mailer: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer layout 'awesome' # use awesome.(html|text).erb as the layout end ``` @@ -381,7 +429,7 @@ You can also pass in a `layout: 'layout_name'` option to the render call inside the format block to specify different layouts for different formats: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user) mail(to: user.email) do |format| format.html { render layout: 'my_layout' } @@ -406,18 +454,25 @@ globally in `config/application.rb`: config.action_mailer.default_url_options = { host: 'example.com' } ``` -#### generating URLs with `url_for` +Because of this behavior you cannot use any of the `*_path` helpers inside of +an email. Instead you will need to use the associated `*_url` helper. For example +instead of using + +``` +<%= link_to 'welcome', welcome_path %> +``` -You need to pass the `only_path: false` option when using `url_for`. This will -ensure that absolute URLs are generated because the `url_for` view helper will, -by default, generate relative URLs when a `:host` option isn't explicitly -provided. +You will need to use: -```erb -<%= url_for(controller: 'welcome', - action: 'greeting', - only_path: false) %> ``` +<%= link_to 'welcome', welcome_url %> +``` + +By using the full URL, your links will now work in your emails. + +#### generating URLs with `url_for` + +`url_for` generate full URL by default in templates. If you did not configure the `:host` option globally make sure to pass it to `url_for`. @@ -429,9 +484,6 @@ If you did not configure the `:host` option globally make sure to pass it to action: 'greeting') %> ``` -NOTE: When you explicitly pass the `:host` Rails will always generate absolute -URLs, so there is no need to pass `only_path: false`. - #### generating URLs with named routes Email clients have no web context and so paths have no base URL to form complete @@ -463,7 +515,7 @@ while delivering emails, you can do this using `delivery_method_options` in the mailer action. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user, company) @user = user @url = user_url(@user) @@ -485,7 +537,7 @@ option. In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user, email_body) mail(to: user.email, body: email_body, @@ -515,7 +567,7 @@ mailer, and pass the email object to the mailer `receive` instance method. Here's an example: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def receive(email) page = Page.find_by(address: email.to.first) page.emails.create( @@ -551,7 +603,7 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and using instance variables set in your mailer action. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers @@ -608,16 +660,16 @@ files (environment.rb, production.rb, etc...) | Configuration | Description | |---------------|-------------| |`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.| -|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| +|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| -|`delivery_method`|Defines a delivery method. Possible values are `:smtp` (default), `:sendmail`, `:file` and `:test`.| +|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.| |`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| |`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| |`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).| For a complete writeup of possible configurations see the -[Action Mailer section](configuring.html#configuring-action-mailer) in +[Configuring Action Mailer](configuring.html#configuring-action-mailer) in our Configuring Rails Applications guide. ### Example Action Mailer Configuration @@ -639,8 +691,8 @@ config.action_mailer.default_options = {from: 'no-reply@example.com'} ### Action Mailer Configuration for Gmail -As Action Mailer now uses the Mail gem, this becomes as simple as adding to your -`config/environments/$RAILS_ENV.rb` file: +As Action Mailer now uses the [Mail gem](https://github.com/mikel/mail), this +becomes as simple as adding to your `config/environments/$RAILS_ENV.rb` file: ```ruby config.action_mailer.delivery_method = :smtp @@ -662,6 +714,7 @@ You can find detailed instructions on how to test your mailers in the Intercepting Emails ------------------- + There are situations where you need to edit an email before it's delivered. Fortunately Action Mailer provides hooks to intercept every email. You can register an interceptor to make modifications to mail messages @@ -680,10 +733,12 @@ Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb` ```ruby -ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging? +if Rails.env.staging? + ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) +end ``` NOTE: The example above uses a custom environment called "staging" for a production like server but for testing purposes. You can read -[Creating Rails environments](./configuring.html#creating-rails-environments) +[Creating Rails environments](configuring.html#creating-rails-environments) for more information about custom Rails environments. diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 6a355a5177..665a2b71ff 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Action View Overview ==================== @@ -7,7 +9,6 @@ After reading this guide, you will know: * How best to use templates, partials, and layouts. * What helpers are provided by Action View and how to make your own. * How to use localized views. -* How to use Action View outside of Rails. -------------------------------------------------------------------------------- @@ -28,34 +29,34 @@ For each controller there is an associated directory in the `app/views` director Let's take a look at what Rails does by default when creating a new resource using the scaffold generator: ```bash -$ rails generate scaffold post +$ bin/rails generate scaffold article [...] invoke scaffold_controller - create app/controllers/posts_controller.rb + create app/controllers/articles_controller.rb invoke erb - create app/views/posts - create app/views/posts/index.html.erb - create app/views/posts/edit.html.erb - create app/views/posts/show.html.erb - create app/views/posts/new.html.erb - create app/views/posts/_form.html.erb + create app/views/articles + create app/views/articles/index.html.erb + create app/views/articles/edit.html.erb + create app/views/articles/show.html.erb + create app/views/articles/new.html.erb + create app/views/articles/_form.html.erb [...] ``` There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above. -For example, the index controller action of the `posts_controller.rb` will use the `index.html.erb` view file in the `app/views/posts` directory. -The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of these three components. +For example, the index controller action of the `articles_controller.rb` will use the `index.html.erb` view file in the `app/views/articles` directory. +The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Within this guide you will find more detailed documentation about each of these three components. Templates, Partials and Layouts ------------------------------- -As mentioned before, the final HTML output is a composition of three Rails elements: `Templates`, `Partials` and `Layouts`. -Below is a brief overview of each one of them. +As mentioned, the final HTML output is a composition of three Rails elements: `Templates`, `Partials` and `Layouts`. +Below is a brief overview of each of them. ### Templates -Action View templates can be written in several ways. If the template file has a `.erb` extension then it uses a mixture of ERB (included in Ruby) and HTML. If the template file has a `.builder` extension then a fresh instance of `Builder::XmlMarkup` library is used. +Action View templates can be written in several ways. If the template file has a `.erb` extension then it uses a mixture of ERB (Embedded Ruby) and HTML. If the template file has a `.builder` extension then the `Builder::XmlMarkup` library is used. Rails supports multiple template systems and uses a file extension to distinguish amongst them. For example, an HTML file using the ERB template system will have `.html.erb` as a file extension. @@ -72,7 +73,7 @@ Consider the following loop for names: <% end %> ``` -The loop is set up in regular embedding tags (`<% %>`) and the name is written using the output embedding tags (`<%= %>`). Note that this is not just a usage suggestion, for regular output functions like `print` or `puts` won't work with ERB templates. So this would be wrong: +The loop is set up using regular embedding tags (`<% %>`) and the name is inserted using the output embedding tags (`<%= %>`). Note that this is not just a usage suggestion: regular output functions such as `print` and `puts` won't be rendered to the view with ERB templates. So this would be wrong: ```html+erb <%# WRONG %> @@ -231,7 +232,7 @@ The `object` and `as` options can also be used together: #### Rendering Collections -It is very common that a template needs to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. +It is very common that a template will need to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. So this example for rendering all the products: @@ -247,7 +248,7 @@ can be rewritten in a single line: <%= render partial: "product", collection: @products %> ``` -When a partial is called like this (eg. with a collection), the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is `_product`, and within it you can refer to `product` to get the instance that is being rendered. +When a partial is called with a collection, the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is `_product`, and within it you can refer to `product` to get the collection member that is being rendered. You can use a shorthand syntax for rendering collections. Assuming `@products` is a collection of `Product` instances, you can simply write the following to produce the same result: @@ -255,7 +256,7 @@ You can use a shorthand syntax for rendering collections. Assuming `@products` i <%= render @products %> ``` -Rails determines the name of the partial to use by looking at the model name in the collection, `Product` in this case. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection. +Rails determines the name of the partial to use by looking at the model name in the collection, `Product` in this case. In fact, you can even render a collection made up of instances of different models using this shorthand, and Rails will choose the proper partial for each member of the collection. #### Spacer Templates @@ -269,30 +270,30 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe ### Layouts -Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails application has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. +Layouts can be used to render a common view template around the results of Rails controller actions. Typically, a Rails application will have a couple of layouts that pages will be rendered within. For example, a site might have one layout for a logged in user and another for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us" pages. You would expect each layout to have a different look and feel. You can read about layouts in more detail in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. Partial Layouts --------------- -Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion. +Partials can have their own layouts applied to them. These layouts are different from those applied to a controller action, but they work in a similar fashion. -Let's say we're displaying a post on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Post`: +Let's say we're displaying an article on a page which should be wrapped in a `div` for display purposes. Firstly, we'll create a new `Article`: ```ruby -Post.create(body: 'Partial Layouts are cool!') +Article.create(body: 'Partial Layouts are cool!') ``` -In the `show` template, we'll render the `_post` partial wrapped in the `box` layout: +In the `show` template, we'll render the `_article` partial wrapped in the `box` layout: -**posts/show.html.erb** +**articles/show.html.erb** ```erb -<%= render partial: 'post', layout: 'box', locals: {post: @post} %> +<%= render partial: 'article', layout: 'box', locals: {article: @article} %> ``` -The `box` layout simply wraps the `_post` partial in a `div`: +The `box` layout simply wraps the `_article` partial in a `div`: -**posts/_box.html.erb** +**articles/_box.html.erb** ```html+erb <div class='box'> @@ -300,13 +301,13 @@ The `box` layout simply wraps the `_post` partial in a `div`: </div> ``` -The `_post` partial wraps the post's `body` in a `div` with the `id` of the post using the `div_for` helper: +The `_article` partial wraps the article's `body` in a `div` with the `id` of the article using the `div_for` helper: -**posts/_post.html.erb** +**articles/_article.html.erb** ```html+erb -<%= div_for(post) do %> - <p><%= post.body %></p> +<%= div_for(article) do %> + <p><%= article.body %></p> <% end %> ``` @@ -314,22 +315,22 @@ this would output the following: ```html <div class='box'> - <div id='post_1'> + <div id='article_1'> <p>Partial Layouts are cool!</p> </div> </div> ``` -Note that the partial layout has access to the local `post` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. +Note that the partial layout has access to the local `article` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. -You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_post` partial, we could do this instead: +You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_article` partial, we could do this instead: -**posts/show.html.erb** +**articles/show.html.erb** ```html+erb -<% render(layout: 'box', locals: {post: @post}) do %> - <%= div_for(post) do %> - <p><%= post.body %></p> +<% render(layout: 'box', locals: {article: @article}) do %> + <%= div_for(article) do %> + <p><%= article.body %></p> <% end %> <% end %> ``` @@ -356,18 +357,18 @@ This module provides methods for generating container tags, such as `div`, for y Renders a container tag that relates to your Active Record Object. -For example, given `@post` is the object of `Post` class, you can do: +For example, given `@article` is the object of `Article` class, you can do: ```html+erb -<%= content_tag_for(:tr, @post) do %> - <td><%= @post.title %></td> +<%= content_tag_for(:tr, @article) do %> + <td><%= @article.title %></td> <% end %> ``` This will generate this HTML output: ```html -<tr id="post_1234" class="post"> +<tr id="article_1234" class="article"> <td>Hello World!</td> </tr> ``` @@ -375,34 +376,34 @@ This will generate this HTML output: You can also supply HTML attributes as an additional option hash. For example: ```html+erb -<%= content_tag_for(:tr, @post, class: "frontpage") do %> - <td><%= @post.title %></td> +<%= content_tag_for(:tr, @article, class: "frontpage") do %> + <td><%= @article.title %></td> <% end %> ``` Will generate this HTML output: ```html -<tr id="post_1234" class="post frontpage"> +<tr id="article_1234" class="article frontpage"> <td>Hello World!</td> </tr> ``` -You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@posts` is an array of two `Post` objects: +You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@articles` is an array of two `Article` objects: ```html+erb -<%= content_tag_for(:tr, @posts) do |post| %> - <td><%= post.title %></td> +<%= content_tag_for(:tr, @articles) do |article| %> + <td><%= article.title %></td> <% end %> ``` Will generate this HTML output: ```html -<tr id="post_1234" class="post"> +<tr id="article_1234" class="article"> <td>Hello World!</td> </tr> -<tr id="post_1235" class="post"> +<tr id="article_1235" class="article"> <td>Ruby on Rails Rocks!</td> </tr> ``` @@ -412,15 +413,15 @@ Will generate this HTML output: This is actually a convenient method which calls `content_tag_for` internally with `:div` as the tag name. You can pass either an Active Record object or a collection of objects. For example: ```html+erb -<%= div_for(@post, class: "frontpage") do %> - <td><%= @post.title %></td> +<%= div_for(@article, class: "frontpage") do %> + <td><%= @article.title %></td> <% end %> ``` Will generate this HTML output: ```html -<div id="post_1234" class="post frontpage"> +<div id="article_1234" class="article frontpage"> <td>Hello World!</td> </div> ``` @@ -495,7 +496,7 @@ image_url("edit.png") # => http://www.example.com/assets/edit.png #### image_tag -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. +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" /> @@ -503,7 +504,7 @@ image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" /> #### javascript_include_tag -Returns an html script tag for each of the sources provided. You can pass in the filename (`.js` extension is optional) of JavaScript files that exist in your `app/assets/javascripts` directory for inclusion into the current page or you can pass the full path relative to your document root. +Returns an HTML script tag for each of the sources provided. You can pass in the filename (`.js` extension is optional) of JavaScript files that exist in your `app/assets/javascripts` directory for inclusion into the current page or you can pass the full path relative to your document root. ```ruby javascript_include_tag "common" # => <script src="/assets/common.js"></script> @@ -590,14 +591,14 @@ This helper makes building an Atom feed easy. Here's a full usage example: **config/routes.rb** ```ruby -resources :posts +resources :articles ``` -**app/controllers/posts_controller.rb** +**app/controllers/articles_controller.rb** ```ruby def index - @posts = Post.all + @articles = Article.all respond_to do |format| format.html @@ -606,20 +607,20 @@ def index end ``` -**app/views/posts/index.atom.builder** +**app/views/articles/index.atom.builder** ```ruby atom_feed do |feed| - feed.title("Posts Index") - feed.updated((@posts.first.created_at)) + feed.title("Articles Index") + feed.updated((@articles.first.created_at)) - @posts.each do |post| - feed.entry(post) do |entry| - entry.title(post.title) - entry.content(post.body, type: 'html') + @articles.each do |article| + feed.entry(article) do |entry| + entry.title(article.title) + entry.content(article.body, type: 'html') entry.author do |author| - author.name(post.author_name) + author.name(article.author_name) end end end @@ -697,7 +698,7 @@ For example, let's say we have a standard application layout, but also a special </html> ``` -**app/views/posts/special.html.erb** +**app/views/articles/special.html.erb** ```html+erb <p>This is a special page.</p> @@ -714,7 +715,7 @@ For example, let's say we have a standard application layout, but also a special Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute. ```ruby -date_select("post", "published_on") +date_select("article", "published_on") ``` #### datetime_select @@ -722,7 +723,7 @@ date_select("post", "published_on") Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based attribute. ```ruby -datetime_select("post", "published_on") +datetime_select("article", "published_on") ``` #### distance_of_time_in_words @@ -736,7 +737,7 @@ distance_of_time_in_words(Time.now, Time.now + 15.seconds, include_seconds: true #### select_date -Returns a set of html select-tags (one for year, month, and day) pre-selected with the `date` provided. +Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the `date` provided. ```ruby # Generates a date select that defaults to the date provided (six days after today) @@ -748,7 +749,7 @@ select_date() #### select_datetime -Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the `datetime` provided. +Returns a set of HTML select-tags (one for year, month, day, hour, and minute) pre-selected with the `datetime` provided. ```ruby # Generates a datetime select that defaults to the datetime provided (four days after today) @@ -808,7 +809,7 @@ select_second(Time.now + 16.minutes) #### select_time -Returns a set of html select-tags (one for hour and minute). +Returns a set of HTML select-tags (one for hour and minute). ```ruby # Generates a time select that defaults to the time provided @@ -904,10 +905,10 @@ The params hash has a nested person value, which can therefore be accessed with Returns a checkbox tag tailored for accessing a specified attribute. ```ruby -# Let's say that @post.validated? is 1: -check_box("post", "validated") -# => <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> -# <input name="post[validated]" type="hidden" value="0" /> +# Let's say that @article.validated? is 1: +check_box("article", "validated") +# => <input type="checkbox" id="article_validated" name="article[validated]" value="1" /> +# <input name="article[validated]" type="hidden" value="0" /> ``` #### fields_for @@ -939,7 +940,7 @@ file_field(:user, :avatar) Creates a form and a scope around a specific model object that is used as a base for questioning about values for the fields. ```html+erb -<%= form_for @post do |f| %> +<%= form_for @article do |f| %> <%= f.label :title, 'Title' %>: <%= f.text_field :title %><br> <%= f.label :body, 'Body' %>: @@ -961,8 +962,8 @@ hidden_field(:user, :token) Returns a label tag tailored for labelling an input field for a specified attribute. ```ruby -label(:post, :title) -# => <label for="post_title">Title</label> +label(:article, :title) +# => <label for="article_title">Title</label> ``` #### password_field @@ -979,11 +980,11 @@ password_field(:login, :pass) Returns a radio button tag for accessing a specified attribute. ```ruby -# Let's say that @post.category returns "rails": -radio_button("post", "category", "rails") -radio_button("post", "category", "java") -# => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> -# <input type="radio" id="post_category_java" name="post[category]" value="java" /> +# Let's say that @article.category returns "rails": +radio_button("article", "category", "rails") +radio_button("article", "category", "java") +# => <input type="radio" id="article_category_rails" name="article[category]" value="rails" checked="checked" /> +# <input type="radio" id="article_category_java" name="article[category]" value="java" /> ``` #### text_area @@ -1002,8 +1003,8 @@ text_area(:comment, :text, size: "20x30") Returns an input tag of the "text" type tailored for accessing a specified attribute. ```ruby -text_field(:post, :title) -# => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" /> +text_field(:article, :title) +# => <input type="text" id="article_title" name="article[title]" value="#{@article.title}" /> ``` #### email_field @@ -1035,28 +1036,28 @@ Returns `select` and `option` tags for the collection of existing return values Example object structure for use with this method: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base belongs_to :author end class Author < ActiveRecord::Base - has_many :posts + has_many :articles def name_with_initial "#{first_name.first}. #{last_name}" end end ``` -Sample usage (selecting the associated Author for an instance of Post, `@post`): +Sample usage (selecting the associated Author for an instance of Article, `@article`): ```ruby -collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {prompt: true}) +collection_select(:article, :author_id, Author.all, :id, :name_with_initial, {prompt: true}) ``` -If `@post.author_id` is 1, this would return: +If `@article.author_id` is 1, this would return: ```html -<select name="post[author_id]"> +<select name="article[author_id]"> <option value="">Please select</option> <option value="1" selected="selected">D. Heinemeier Hansson</option> <option value="2">D. Thomas</option> @@ -1071,33 +1072,33 @@ Returns `radio_button` tags for the collection of existing return values of `met Example object structure for use with this method: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base belongs_to :author end class Author < ActiveRecord::Base - has_many :posts + has_many :articles def name_with_initial "#{first_name.first}. #{last_name}" end end ``` -Sample usage (selecting the associated Author for an instance of Post, `@post`): +Sample usage (selecting the associated Author for an instance of Article, `@article`): ```ruby -collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) +collection_radio_buttons(:article, :author_id, Author.all, :id, :name_with_initial) ``` -If `@post.author_id` is 1, this would return: +If `@article.author_id` is 1, this would return: ```html -<input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" /> -<label for="post_author_id_1">D. Heinemeier Hansson</label> -<input id="post_author_id_2" name="post[author_id]" type="radio" value="2" /> -<label for="post_author_id_2">D. Thomas</label> -<input id="post_author_id_3" name="post[author_id]" type="radio" value="3" /> -<label for="post_author_id_3">M. Clark</label> +<input id="article_author_id_1" name="article[author_id]" type="radio" value="1" checked="checked" /> +<label for="article_author_id_1">D. Heinemeier Hansson</label> +<input id="article_author_id_2" name="article[author_id]" type="radio" value="2" /> +<label for="article_author_id_2">D. Thomas</label> +<input id="article_author_id_3" name="article[author_id]" type="radio" value="3" /> +<label for="article_author_id_3">M. Clark</label> ``` #### collection_check_boxes @@ -1107,34 +1108,34 @@ Returns `check_box` tags for the collection of existing return values of `method Example object structure for use with this method: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_and_belongs_to_many :authors end class Author < ActiveRecord::Base - has_and_belongs_to_many :posts + has_and_belongs_to_many :articles def name_with_initial "#{first_name.first}. #{last_name}" end end ``` -Sample usage (selecting the associated Authors for an instance of Post, `@post`): +Sample usage (selecting the associated Authors for an instance of Article, `@article`): ```ruby -collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) +collection_check_boxes(:article, :author_ids, Author.all, :id, :name_with_initial) ``` -If `@post.author_ids` is [1], this would return: +If `@article.author_ids` is [1], this would return: ```html -<input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" /> -<label for="post_author_ids_1">D. Heinemeier Hansson</label> -<input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" /> -<label for="post_author_ids_2">D. Thomas</label> -<input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" /> -<label for="post_author_ids_3">M. Clark</label> -<input name="post[author_ids][]" type="hidden" value="" /> +<input id="article_author_ids_1" name="article[author_ids][]" type="checkbox" value="1" checked="checked" /> +<label for="article_author_ids_1">D. Heinemeier Hansson</label> +<input id="article_author_ids_2" name="article[author_ids][]" type="checkbox" value="2" /> +<label for="article_author_ids_2">D. Thomas</label> +<input id="article_author_ids_3" name="article[author_ids][]" type="checkbox" value="3" /> +<label for="article_author_ids_3">M. Clark</label> +<input name="article[author_ids][]" type="hidden" value="" /> ``` #### country_options_for_select @@ -1222,13 +1223,13 @@ Create a select tag and a series of contained option tags for the provided objec Example: ```ruby -select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true}) +select("article", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true}) ``` -If `@post.person_id` is 1, this would become: +If `@article.person_id` is 1, this would become: ```html -<select name="post[person_id]"> +<select name="article[person_id]"> <option value=""></option> <option value="1" selected="selected">David</option> <option value="2">Sam</option> @@ -1303,10 +1304,10 @@ file_field_tag 'attachment' Starts a form tag that points the action to an url configured with `url_for_options` just like `ActionController::Base#url_for`. ```html+erb -<%= form_tag '/posts' do %> +<%= form_tag '/articles' do %> <div><%= submit_tag 'Save' %></div> <% end %> -# => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form> +# => <form action="/articles" method="post"><div><input type="submit" name="submit" value="Save" /></div></form> ``` #### hidden_field_tag @@ -1368,8 +1369,8 @@ select_tag "people", "<option>David</option>" Creates a submit button with the text provided as the caption. ```ruby -submit_tag "Publish this post" -# => <input name="commit" type="submit" value="Publish this post" /> +submit_tag "Publish this article" +# => <input name="commit" type="submit" value="Publish this article" /> ``` #### text_area_tag @@ -1377,8 +1378,8 @@ submit_tag "Publish this post" Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. ```ruby -text_area_tag 'post' -# => <textarea id="post" name="post"></textarea> +text_area_tag 'article' +# => <textarea id="article" name="article"></textarea> ``` #### text_field_tag @@ -1526,7 +1527,7 @@ The SanitizeHelper module provides a set of methods for scrubbing text of undesi #### sanitize -This sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed. +This sanitize helper will HTML encode all tags and strip all attributes that aren't specifically allowed. ```ruby sanitize @article.body @@ -1550,7 +1551,7 @@ end Sanitizes a block of CSS code. -#### strip_links(html) +#### strip_links(html) Strips all link tags from text leaving just the link text. ```ruby @@ -1568,9 +1569,9 @@ strip_links('Blog: <a href="http://myblog.com/">Visit</a>.') # => Blog: Visit. ``` -#### strip_tags(html) +#### strip_tags(html) -Strips all HTML tags from the html, including comments. +Strips all HTML tags from the html, including comments. This uses the html-scanner tokenizer and so its HTML parsing ability is limited by that of html-scanner. ```ruby @@ -1585,13 +1586,24 @@ strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>") NB: The output may still contain unescaped '<', '>', '&' characters and confuse browsers. +### CsrfHelper + +Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site +request forgery protection parameter and token, respectively. + +```html +<%= csrf_meta_tags %> +``` + +NOTE: Regular forms generate hidden fields so they do not use these tags. More +details can be found in the [Rails Security Guide](security.html#cross-site-request-forgery-csrf). Localized Views --------------- Action View has the ability render different templates depending on the current locale. -For example, suppose you have a `PostsController` with a show action. By default, calling this action will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/posts/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. +For example, suppose you have a `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. You can use the same technique to localize the rescue files in your public directory. For example, setting `I18n.locale = :de` and creating `public/500.de.html` and `public/404.de.html` would allow you to have localized rescue pages. @@ -1605,6 +1617,6 @@ def set_expert_locale end ``` -Then you could create special views like `app/views/posts/show.expert.html.erb` that would only be displayed to expert users. +Then you could create special views like `app/views/articles/show.expert.html.erb` that would only be displayed to expert users. You can read more about the Rails Internationalization (I18n) API [here](i18n.html). diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md new file mode 100644 index 0000000000..53588bdd1d --- /dev/null +++ b/guides/source/active_job_basics.md @@ -0,0 +1,319 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Active Job Basics +================= + +This guide provides you with all you need to get started in creating, +enqueueing and executing background jobs. + +After reading this guide, you will know: + +* How to create jobs. +* How to enqueue jobs. +* How to run jobs in the background. +* How to send emails from your application async. + +-------------------------------------------------------------------------------- + + +Introduction +------------ + +Active Job is a framework for declaring jobs and making them run on a variety +of queueing backends. These jobs can be everything from regularly scheduled +clean-ups, to billing charges, to mailings. Anything that can be chopped up +into small units of work and run in parallel, really. + + +The Purpose of Active Job +----------------------------- +The main point is to ensure that all Rails apps will have a job infrastructure +in place, even if it's in the form of an "immediate runner". We can then have +framework features and other gems build on top of that, without having to +worry about API differences between various job runners such as Delayed Job +and Resque. Picking your queuing backend becomes more of an operational concern, +then. And you'll be able to switch between them without having to rewrite your jobs. + + +Creating a Job +-------------- + +This section will provide a step-by-step guide to creating a job and enqueuing it. + +### Create the Job + +Active Job provides a Rails generator to create jobs. The following will create a +job in `app/jobs` (with an attached test case under `test/jobs`): + +```bash +$ bin/rails generate job guests_cleanup +invoke test_unit +create test/jobs/guests_cleanup_job_test.rb +create app/jobs/guests_cleanup_job.rb +``` + +You can also create a job that will run on a specific queue: + +```bash +$ bin/rails generate job guests_cleanup --queue urgent +``` + +If you don't want to use a generator, you could create your own file inside of +`app/jobs`, just make sure that it inherits from `ActiveJob::Base`. + +Here's what a job looks like: + +```ruby +class GuestsCleanupJob < ActiveJob::Base + queue_as :default + + def perform(*args) + # Do something later + end +end +``` + +### Enqueue the Job + +Enqueue a job like so: + +```ruby +# Enqueue a job to be performed as soon the queueing system is free. +MyJob.perform_later record +``` + +```ruby +# Enqueue a job to be performed tomorrow at noon. +MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) +``` + +```ruby +# Enqueue a job to be performed 1 week from now. +MyJob.set(wait: 1.week).perform_later(record) +``` + +That's it! + + +Job Execution +------------- + +If no adapter is set, the job is immediately executed. + +### Backends + +Active Job has built-in adapters for multiple queueing backends (Sidekiq, +Resque, Delayed Job and others). To get an up-to-date list of the adapters +see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). + +### Setting the Backend + +You can easily set your queueing backend: + +```ruby +# config/application.rb +module YourApp + class Application < Rails::Application + # Be sure to have the adapter's gem in your Gemfile and follow + # the adapter's specific installation and deployment instructions. + config.active_job.queue_adapter = :sidekiq + end +end +``` + + +Queues +------ + +Most of the adapters support multiple queues. With Active Job you can schedule +the job to run on a specific queue: + +```ruby +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end +``` + +You can prefix the queue name for all your jobs using +`config.active_job.queue_name_prefix` in `application.rb`: + +```ruby +# config/application.rb +module YourApp + class Application < Rails::Application + config.active_job.queue_name_prefix = Rails.env + end +end + +# app/jobs/guests_cleanup.rb +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end + +# Now your job will run on queue production_low_priority on your +# production environment and on staging_low_priority on your staging +# environment +``` + +The default queue name prefix delimiter is '\_'. This can be changed by setting +`config.active_job.queue_name_delimiter` in `application.rb`: + +```ruby +# config/application.rb +module YourApp + class Application < Rails::Application + config.active_job.queue_name_prefix = Rails.env + config.active_job.queue_name_delimiter = '.' + end +end + +# app/jobs/guests_cleanup.rb +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end + +# Now your job will run on queue production.low_priority on your +# production environment and on staging.low_priority on your staging +# environment +``` + +If you want more control on what queue a job will be run you can pass a `:queue` +option to `#set`: + +```ruby +MyJob.set(queue: :another_queue).perform_later(record) +``` + +To control the queue from the job level you can pass a block to `#queue_as`. The +block will be executed in the job context (so you can access `self.arguments`) +and you must return the queue name: + +```ruby +class ProcessVideoJob < ActiveJob::Base + queue_as do + video = self.arguments.first + if video.owner.premium? + :premium_videojobs + else + :videojobs + end + end + + def perform(video) + # do process video + end +end + +ProcessVideoJob.perform_later(Video.last) +``` + +NOTE: Make sure your queueing backend "listens" on your queue name. For some +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 + +```ruby +class GuestsCleanupJob < ActiveJob::Base + 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 + + def perform + # Do something later + end +end +``` + + +Action Mailer +------------ + +One of the most common jobs in a modern web application is sending emails outside +of the request-response cycle, so the user doesn't have to wait on it. Active Job +is integrated with Action Mailer so you can easily send emails asynchronously: + +```ruby +# If you want to send the email now use #deliver_now +UserMailer.welcome(@user).deliver_now + +# If you want to send the email through Active Job use #deliver_later +UserMailer.welcome(@user).deliver_later +``` + + +GlobalID +-------- + +Active Job supports GlobalID for parameters. This makes it possible to pass live +Active Record objects to your job instead of class/id pairs, which you then have +to manually deserialize. Before, jobs would look like this: + +```ruby +class TrashableCleanupJob < ActiveJob::Base + def perform(trashable_class, trashable_id, depth) + trashable = trashable_class.constantize.find(trashable_id) + trashable.cleanup(depth) + end +end +``` + +Now you can simply do: + +```ruby +class TrashableCleanupJob < ActiveJob::Base + def perform(trashable, depth) + trashable.cleanup(depth) + end +end +``` + +This works with any class that mixes in `GlobalID::Identification`, which +by default has been mixed into Active Model classes. + + +Exceptions +---------- + +Active Job provides a way to catch exceptions raised during the execution of the +job: + +```ruby +class GuestsCleanupJob < ActiveJob::Base + queue_as :default + + rescue_from(ActiveRecord::RecordNotFound) do |exception| + # do something with the exception + end + + def perform + # Do something later + end +end +``` diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index 0019d08328..4b2bfaee2f 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -1,20 +1,34 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Model Basics =================== -This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-Active Record models. Active Model also helps building custom ORMs for use outside of the Rails framework. +This guide should provide you with all you need to get started using model +classes. Active Model allows for Action Pack helpers to interact with +plain Ruby objects. Active Model also helps build custom ORMs for use +outside of the Rails framework. + +After reading this guide, you will be able to add to plain Ruby objects: -After reading this guide, you will know: +* The ability to behave like an Active Record model. +* Callbacks and validations like Active Record. +* Serializers. +* Integration with the Rails internationalization (i18n) framework. -------------------------------------------------------------------------------- Introduction ------------ -Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below. +Active Model is a library containing various modules used in developing +classes that need some features present on Active Record. +Some of these modules are explained below. -### AttributeMethods +### Attribute Methods -The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes and which methods on the object will use them. +The `ActiveModel::AttributeMethods` module can add custom prefixes and suffixes +on methods of a class. It is used by defining the prefixes and suffixes and +which methods on the object will use them. ```ruby class Person @@ -38,14 +52,17 @@ end person = Person.new person.age = 110 -person.age_highest? # true -person.reset_age # 0 -person.age_highest? # false +person.age_highest? # => true +person.reset_age # => 0 +person.age_highest? # => false ``` ### Callbacks -Callbacks gives Active Record style callbacks. This provides an ability to define callbacks which run at appropriate times. After defining callbacks, you can wrap them with before, after and around custom methods. +`ActiveModel::Callbacks` gives Active Record style callbacks. This provides an +ability to define callbacks which run at appropriate times. +After defining callbacks, you can wrap them with before, after and around +custom methods. ```ruby class Person @@ -69,7 +86,9 @@ end ### Conversion -If a class defines `persisted?` and `id` methods, then you can include the `Conversion` module in that class and call the Rails conversion methods on objects of that class. +If a class defines `persisted?` and `id` methods, then you can include the +`ActiveModel::Conversion` module in that class and call the Rails conversion +methods on objects of that class. ```ruby class Person @@ -92,11 +111,13 @@ person.to_param # => nil ### Dirty -An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes `first_name` and `last_name`: +An object becomes dirty when it has gone through one or more changes to its +attributes and has not been saved. `ActiveModel::Dirty` gives the ability to +check whether an object has been changed or not. It also has attribute based +accessor methods. Let's consider a Person class with attributes `first_name` +and `last_name`: ```ruby -require 'active_model' - class Person include ActiveModel::Dirty define_attribute_methods :first_name, :last_name @@ -162,10 +183,11 @@ Track what was the previous value of the attribute. ```ruby # attr_name_was accessor -person.first_name_was # => "First Name" +person.first_name_was # => nil ``` -Track both previous and current value of the changed attribute. Returns an array if changed, else returns nil. +Track both previous and current value of the changed attribute. Returns an array +if changed, else returns nil. ```ruby # attr_name_change @@ -175,7 +197,8 @@ person.last_name_change # => nil ### Validations -Validations module adds the ability to class objects to validate them in Active Record style. +`ActiveModel::Validations` module adds the ability to validate class objects +like in Active Record. ```ruby class Person @@ -188,7 +211,8 @@ class Person validates! :token, presence: true end -person = Person.new(token: "2b1f325") +person = Person.new +person.token = "2b1f325" person.valid? # => false person.name = 'vishnu' person.email = 'me' @@ -198,3 +222,335 @@ person.valid? # => true person.token = nil person.valid? # => raises ActiveModel::StrictValidationFailed ``` + +### Naming + +`ActiveModel::Naming` adds a number of class methods which make the 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. + +```ruby +class Person + extend ActiveModel::Naming +end + +Person.model_name.name # => "Person" +Person.model_name.singular # => "person" +Person.model_name.plural # => "people" +Person.model_name.element # => "person" +Person.model_name.human # => "Person" +Person.model_name.collection # => "people" +Person.model_name.param_key # => "person" +Person.model_name.i18n_key # => :person +Person.model_name.route_key # => "people" +Person.model_name.singular_route_key # => "person" +``` + +### Model + +`ActiveModel::Model` adds the ability to a class to work with Action Pack and +Action View right out of the box. + +```ruby +class EmailContact + include ActiveModel::Model + + attr_accessor :name, :email, :message + validates :name, :email, :message, presence: true + + def deliver + if valid? + # deliver email + end + end +end +``` + +When including `ActiveModel::Model` you get some features like: + +- model name introspection +- conversions +- translations +- validations + +It also gives you the ability to initialize an object with a hash of attributes, +much like any Active Record object. + +```ruby +email_contact = EmailContact.new(name: 'David', + email: 'david@example.com', + message: 'Hello World') +email_contact.name # => 'David' +email_contact.email # => 'david@example.com' +email_contact.valid? # => true +email_contact.persisted? # => false +``` + +Any class that includes `ActiveModel::Model` can be used with `form_for`, +`render` and any other Action View helper methods, just like Active Record +objects. + +### Serialization + +`ActiveModel::Serialization` provides a basic serialization for your object. +You need to declare an attributes hash which contains the attributes you want to +serialize. Attributes must be strings, not symbols. + +```ruby +class Person + include ActiveModel::Serialization + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +Now you can access a serialized hash of your object using the `serializable_hash`. + +```ruby +person = Person.new +person.serializable_hash # => {"name"=>nil} +person.name = "Bob" +person.serializable_hash # => {"name"=>"Bob"} +``` + +#### ActiveModel::Serializers + +Rails provides two serializers `ActiveModel::Serializers::JSON` and +`ActiveModel::Serializers::Xml`. Both of these modules automatically include +the `ActiveModel::Serialization`. + +##### ActiveModel::Serializers::JSON + +To use the `ActiveModel::Serializers::JSON` you only need to change from +`ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`. + +```ruby +class Person + include ActiveModel::Serializers::JSON + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +With the `as_json` you have a hash representing the model. + +```ruby +person = Person.new +person.as_json # => {"name"=>nil} +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: + +```ruby +class Person + include ActiveModel::Serializers::JSON + + attr_accessor :name + + def attributes=(hash) + hash.each do |key, value| + send("#{key}=", value) + end + end + + def attributes + {'name' => nil} + end +end +``` + +Now it is possible to create an instance of person and set the attributes using `from_json`. + +```ruby +json = { name: 'Bob' }.to_json +person = Person.new +person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob"> +person.name # => "Bob" +``` + +##### ActiveModel::Serializers::Xml + +To use the `ActiveModel::Serializers::Xml` you only need to change from +`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`. + +```ruby +class Person + include ActiveModel::Serializers::Xml + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +With the `to_xml` you have an XML representing the model. + +```ruby +person = Person.new +person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name nil=\"true\"/>\n</person>\n" +person.name = "Bob" +person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n" +``` + +From an XML string you define the attributes of the model. +You need to have the `attributes=` method defined on your class: + +```ruby +class Person + include ActiveModel::Serializers::Xml + + attr_accessor :name + + def attributes=(hash) + hash.each do |key, value| + send("#{key}=", value) + end + end + + def attributes + {'name' => nil} + end +end +``` + +Now it is possible to create an instance of person and set the attributes using `from_xml`. + +```ruby +xml = { name: 'Bob' }.to_xml +person = Person.new +person.from_xml(xml) # => #<Person:0x00000100c773f0 @name="Bob"> +person.name # => "Bob" +``` + +### Translation + +`ActiveModel::Translation` provides integration between your object and the Rails +internationalization (i18n) framework. + +```ruby +class Person + extend ActiveModel::Translation +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. + +* config/locales/app.pt-BR.yml + + ```yml + pt-BR: + activemodel: + attributes: + person: + name: 'Nome' + ``` + +```ruby +Person.human_attribute_name('name') # => "Nome" +``` + +### Lint Tests + +`ActiveModel::Lint::Tests` allow you to test whether an object is compliant with +the Active Model API. + +* app/models/person.rb + + ```ruby + class Person + include ActiveModel::Model + + end + ``` + +* test/models/person_test.rb + + ```ruby + require 'test_helper' + + class PersonTest < ActiveSupport::TestCase + include ActiveModel::Lint::Tests + + def setup + @model = Person.new + end + end + ``` + +```bash +$ rake test + +Run options: --seed 14596 + +# Running: + +...... + +Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s. + +6 runs, 30 assertions, 0 failures, 0 errors, 0 skips +``` + +An object is not required to implement all APIs in order to work with +Action Pack. This module only intends to provide guidance in case you want all +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 +`has_secure_password` class method is provided which defines +an accessor named `password` with certain validations on it. + +#### Requirements + +`ActiveModel::SecurePassword` depends on the [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), +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. +3. This maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) + +#### Examples + +```ruby +class Person + include ActiveModel::SecurePassword + has_secure_password + attr_accessor :password_digest +end + +person = Person.new + +# When password is blank. +person.valid? # => false + +# When the confirmation doesn't match the password. +person.password = 'aditya' +person.password_confirmation = 'nomatch' +person.valid? # => false + +# When the length of password, exceeds 72. +person.password = person.password_confirmation = 'a' * 100 +person.valid? # => false + +# When all validations are passed. +person.password = person.password_confirmation = 'aditya' +person.valid? # => true +``` diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index a184f0753d..a5196e481e 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Basics ==================== @@ -31,7 +33,7 @@ Object Relational Mapping system. in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring -data access logic is part of the object will educate users of that +data access logic as part of the object will educate users of that object on how to write to and read from the database. ### Object Relational Mapping @@ -60,7 +62,7 @@ Convention over Configuration in Active Record When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by -Rails, you'll need to write very little configuration (in some case no +Rails, you'll need to write very little configuration (in some cases no configuration at all) when creating Active Record models. The idea is that if you configure your applications in the very same way most of the time then this should be the default way. Thus, explicit configuration would be needed @@ -82,13 +84,13 @@ by underscores. Examples: * Model Class - Singular with the first letter of each word capitalized (e.g., `BookClub`). -| Model / Class | Table / Schema | -| ------------- | -------------- | -| `Post` | `posts` | -| `LineItem` | `line_items` | -| `Deer` | `deers` | -| `Mouse` | `mice` | -| `Person` | `people` | +| Model / Class | Table / Schema | +| ---------------- | -------------- | +| `Article` | `articles` | +| `LineItem` | `line_items` | +| `Deer` | `deers` | +| `Mouse` | `mice` | +| `Person` | `people` | ### Schema Conventions @@ -116,13 +118,13 @@ to Active Record instances: locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model. * `type` - Specifies that the model uses [Single Table - Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance). + Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance). * `(association_name)_type` - Stores the type for [polymorphic associations](association_basics.html#polymorphic-associations). * `(table_name)_count` - Used to cache the number of belonging objects on - associations. For example, a `comments_count` column in a `Post` class that + associations. For example, a `comments_count` column in a `Articles` class that has many instances of `Comment` will cache the number of existent comments - for each post. + for each article. NOTE: While these column names are optional, they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, `type` is a reserved keyword used to designate a table using Single Table Inheritance (STI). If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. @@ -309,11 +311,11 @@ into the database. There are several methods that you can use to check your models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. -Validation is a very important issue to consider when persisting to database, so -the methods `create`, `save` and `update` take it into account when +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 database. All of these have a bang counterpart (that -is, `create!`, `save!` and `update!`), which are stricter in that +perform any operation 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: @@ -322,8 +324,9 @@ class User < ActiveRecord::Base validates :name, presence: true end -User.create # => false -User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank +user = User.new +user.save # => false +user.save! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank ``` You can learn more about validations in the [Active Record Validations @@ -357,7 +360,7 @@ class CreatePublications < ActiveRecord::Migration t.string :publisher_type t.boolean :single_issue - t.timestamps + t.timestamps null: false end add_index :publications, :publication_type_id end diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 667433285f..e65ab802c0 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Callbacks ======================= @@ -15,7 +17,7 @@ After reading this guide, you will know: The Object Life Cycle --------------------- -During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object life cycle</em> so that you can control your application and its data. +During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this *object life cycle* so that you can control your application and its data. Callbacks allow you to trigger logic before or after an alteration of an object's state. @@ -92,6 +94,7 @@ Here is a list with all the available Active Record callbacks, listed in the sam * `around_create` * `after_create` * `after_save` +* `after_commit/after_rollback` ### Updating an Object @@ -103,12 +106,14 @@ Here is a list with all the available Active Record callbacks, listed in the sam * `around_update` * `after_update` * `after_save` +* `after_commit/after_rollback` ### Destroying an Object * `before_destroy` * `around_destroy` * `after_destroy` +* `after_commit/after_rollback` 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. @@ -258,27 +263,27 @@ WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by Relational Callbacks -------------------- -Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many posts. A user's posts should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Post` model: +Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many articles. A user's articles should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Article` model: ```ruby class User < ActiveRecord::Base - has_many :posts, dependent: :destroy + has_many :articles, dependent: :destroy end -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base after_destroy :log_destroy_action def log_destroy_action - puts 'Post destroyed' + puts 'Article destroyed' end end >> user = User.first => #<User id: 1> ->> user.posts.create! -=> #<Post id: 1, user_id: 1> +>> user.articles.create! +=> #<Article id: 1, user_id: 1> >> user.destroy -Post destroyed +Article destroyed => #<User id: 1> ``` @@ -325,7 +330,7 @@ When writing conditional callbacks, it is possible to mix both `:if` and `:unles ```ruby class Comment < ActiveRecord::Base after_create :send_email_to_author, if: :author_wants_emails?, - unless: Proc.new { |comment| comment.post.ignore_comments? } + unless: Proc.new { |comment| comment.article.ignore_comments? } end ``` diff --git a/guides/source/migrations.md b/guides/source/active_record_migrations.md index 5d5c2724b1..97cabc1728 100644 --- a/guides/source/migrations.md +++ b/guides/source/active_record_migrations.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Migrations ======================== @@ -18,9 +20,10 @@ After reading this guide, you will know: Migration Overview ------------------ -Migrations are a convenient way to alter your database schema over time 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. +Migrations are a convenient way to +[alter your database schema over time](http://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. You can think of each migration as being a new 'version' of the database. A schema starts off with nothing in it, and each migration modifies it to add or @@ -38,7 +41,7 @@ class CreateProducts < ActiveRecord::Migration t.string :name t.text :description - t.timestamps + t.timestamps null: false end end end @@ -120,7 +123,7 @@ Of course, calculating timestamps is no fun, so Active Record provides a generator to handle making it for you: ```bash -$ rails generate migration AddPartNumberToProducts +$ bin/rails generate migration AddPartNumberToProducts ``` This will create an empty but appropriately named migration: @@ -137,7 +140,7 @@ followed by a list of column names and types then a migration containing the appropriate `add_column` and `remove_column` statements will be created. ```bash -$ rails generate migration AddPartNumberToProducts part_number:string +$ bin/rails generate migration AddPartNumberToProducts part_number:string ``` will generate @@ -153,7 +156,7 @@ end If you'd like to add an index on the new column, you can do that as well: ```bash -$ rails generate migration AddPartNumberToProducts part_number:string:index +$ bin/rails generate migration AddPartNumberToProducts part_number:string:index ``` will generate @@ -171,7 +174,7 @@ end Similarly, you can generate a migration to remove a column from the command line: ```bash -$ rails generate migration RemovePartNumberFromProducts part_number:string +$ bin/rails generate migration RemovePartNumberFromProducts part_number:string ``` generates @@ -187,7 +190,7 @@ end You are not limited to one magically generated column. For example: ```bash -$ rails generate migration AddDetailsToProducts part_number:string price:decimal +$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal ``` generates @@ -206,7 +209,7 @@ followed by a list of column names and types then a migration creating the table XXX with the columns listed will be generated. For example: ```bash -$ rails generate migration CreateProducts name:string part_number:string +$ bin/rails generate migration CreateProducts name:string part_number:string ``` generates @@ -230,7 +233,7 @@ Also, the generator accepts column type as `references`(also available as `belongs_to`). For instance: ```bash -$ rails generate migration AddUserRefToProducts user:references +$ bin/rails generate migration AddUserRefToProducts user:references ``` generates @@ -248,7 +251,7 @@ This migration will create a `user_id` column and appropriate index. There is also a generator which will produce join tables if `JoinTable` is part of the name: ```bash -rails g migration CreateJoinTableCustomerProduct customer product +$ bin/rails g migration CreateJoinTableCustomerProduct customer product ``` will produce the following migration: @@ -272,7 +275,7 @@ relevant table. If you tell Rails what columns you want, then statements for adding these columns will also be created. For example, running: ```bash -$ rails generate model Product name:string description:text +$ bin/rails generate model Product name:string description:text ``` will create a migration that looks like this @@ -284,7 +287,7 @@ class CreateProducts < ActiveRecord::Migration t.string :name t.text :description - t.timestamps + t.timestamps null: false end end end @@ -292,21 +295,15 @@ end You can append as many column name/type pairs as you want. -### Supported Type Modifiers - -You can also specify some options just after the field type between curly -braces. You can use the following modifiers: +### Passing Modifiers -* `limit` Sets the maximum size of the `string/text/binary/integer` fields. -* `precision` Defines the precision for the `decimal` fields, representing the total number of digits in the number. -* `scale` Defines the scale for the `decimal` fields, representing the number of digits after the decimal point. -* `polymorphic` Adds a `type` column for `belongs_to` associations. -* `null` Allows or disallows `NULL` values in the column. +Some commonly used [type modifiers](#column-modifiers) can be passed directly on +the command line. They are enclosed by curly braces and follow the field type: For instance, running: ```bash -$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic} +$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic} ``` will produce a migration that looks like this @@ -320,6 +317,8 @@ class AddDetailsToProducts < ActiveRecord::Migration end ``` +TIP: Have a look at the generators help output for further details. + Writing a Migration ------------------- @@ -414,6 +413,87 @@ end removes the `description` and `name` columns, creates a `part_number` string column and adds an index on it. Finally it renames the `upccode` column. +### Changing Columns + +Like the `remove_column` and `add_column` Rails provides the `change_column` +migration method. + +```ruby +change_column :products, :part_number, :text +``` + +This changes the column `part_number` on products table to be a `:text` field. + +Besides `change_column`, the `change_column_null` and `change_column_default` +methods are used specifically to change the null and default values of a +column. + +```ruby +change_column_null :products, :name, false +change_column_default :products, :approved, false +``` + +This sets `:name` field on products to a `NOT NULL` column and the default +value of the `:approved` field to false. + +TIP: Unlike `change_column` (and `change_column_default`), `change_column_null` +is reversible. + +### Column Modifiers + +Column modifiers can be applied when creating or changing a column: + +* `limit` Sets the maximum size of the `string/text/binary/integer` fields. +* `precision` Defines the precision for the `decimal` fields, representing the +total number of digits in the number. +* `scale` Defines the scale for the `decimal` fields, representing the +number of digits after the decimal point. +* `polymorphic` Adds a `type` column for `belongs_to` associations. +* `null` Allows or disallows `NULL` values in the column. +* `default` Allows to set a default value on the column. Note that if you +are using a dynamic value (such as a date), the default will only be calculated +the first time (i.e. on the date the migration is applied). +* `index` Adds an index for the column. +* `required` Adds `required: true` for `belongs_to` associations and +`null: false` to the column in the migration. + +Some adapters may support additional options; see the adapter specific API docs +for further information. + +### Foreign Keys + +While it's not required you might want to add foreign key constraints to +[guarantee referential integrity](#active-record-and-referential-integrity). + +```ruby +add_foreign_key :articles, :authors +``` + +This adds a new foreign key to the `author_id` column of the `articles` +table. The key references the `id` column of the `authors` table. If the +column names can not be derived from the table names, you can use the +`:column` and `:primary_key` options. + +Rails will generate a name for every foreign key starting with +`fk_rails_` followed by 10 random characters. +There is a `:name` option to specify a different name if needed. + +NOTE: Active Record only supports single column foreign keys. `execute` and +`structure.sql` are required to use composite foreign keys. + +Removing a foreign key is easy as well: + +```ruby +# let Active Record figure out the column name +remove_foreign_key :accounts, :branches + +# remove foreign key for a specific column +remove_foreign_key :accounts, column: :owner_id + +# remove foreign key by name +remove_foreign_key :accounts, name: :special_fk_name +``` + ### When Helpers aren't Enough If the helpers provided by Active Record aren't enough you can use the `execute` @@ -444,6 +524,7 @@ definitions: * `add_index` * `add_reference` * `add_timestamps` +* `add_foreign_key` * `create_table` * `create_join_table` * `drop_table` (must supply a block) @@ -469,24 +550,23 @@ migration what else to do when reverting it. For example: ```ruby class ExampleMigration < ActiveRecord::Migration def change - create_table :products do |t| - t.references :category + create_table :distributors do |t| + t.string :zipcode end reversible do |dir| dir.up do - #add a foreign key + # add a CHECK constraint execute <<-SQL - ALTER TABLE products - ADD CONSTRAINT fk_products_categories - FOREIGN KEY (category_id) - REFERENCES categories(id) + ALTER TABLE distributors + ADD CONSTRAINT zipchk + CHECK (char_length(zipcode) = 5) NO INHERIT; SQL end dir.down do execute <<-SQL - ALTER TABLE products - DROP FOREIGN KEY fk_products_categories + ALTER TABLE distributors + DROP CONSTRAINT zipchk SQL end end @@ -494,12 +574,13 @@ class ExampleMigration < ActiveRecord::Migration add_column :users, :home_page_url, :string rename_column :users, :email, :email_address end +end ``` Using `reversible` will ensure that the instructions are executed in the right order too. If the previous example migration is reverted, the `down` block will be run after the `home_page_url` column is removed and -right before the table `products` is dropped. +right before the table `distributors` is dropped. Sometimes your migration will do something which is just plain irreversible; for example, it might destroy some data. In such cases, you can raise @@ -522,16 +603,15 @@ made in the `up` method. The example in the `reversible` section is equivalent t ```ruby class ExampleMigration < ActiveRecord::Migration def up - create_table :products do |t| - t.references :category + create_table :distributors do |t| + t.string :zipcode end - # add a foreign key + # add a CHECK constraint execute <<-SQL - ALTER TABLE products - ADD CONSTRAINT fk_products_categories - FOREIGN KEY (category_id) - REFERENCES categories(id) + ALTER TABLE distributors + ADD CONSTRAINT zipchk + CHECK (char_length(zipcode) = 5); SQL add_column :users, :home_page_url, :string @@ -543,11 +623,11 @@ class ExampleMigration < ActiveRecord::Migration remove_column :users, :home_page_url execute <<-SQL - ALTER TABLE products - DROP FOREIGN KEY fk_products_categories + ALTER TABLE distributors + DROP CONSTRAINT zipchk SQL - drop_table :products + drop_table :distributors end end ``` @@ -578,43 +658,27 @@ end The `revert` method also accepts a block of instructions to reverse. This could be useful to revert selected parts of previous migrations. For example, let's imagine that `ExampleMigration` is committed and it -is later decided it would be best to serialize the product list instead. -One could write: +is later decided it would be best to use Active Record validations, +in place of the `CHECK` constraint, to verify the zipcode. ```ruby -class SerializeProductListMigration < ActiveRecord::Migration +class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration def change - add_column :categories, :product_list - - reversible do |dir| - dir.up do - # transfer data from Products to Category#product_list - end - dir.down do - # create Products from Category#product_list - end - end - revert do # copy-pasted code from ExampleMigration - create_table :products do |t| - t.references :category - end - reversible do |dir| dir.up do - #add a foreign key + # add a CHECK constraint execute <<-SQL - ALTER TABLE products - ADD CONSTRAINT fk_products_categories - FOREIGN KEY (category_id) - REFERENCES categories(id) + ALTER TABLE distributors + ADD CONSTRAINT zipchk + CHECK (char_length(zipcode) = 5); SQL end dir.down do execute <<-SQL - ALTER TABLE products - DROP FOREIGN KEY fk_products_categories + ALTER TABLE distributors + DROP CONSTRAINT zipchk SQL end end @@ -642,7 +706,7 @@ method for all the migrations that have not yet been run. If there are no such migrations, it exits. It will run these migrations in order based on the date of the migration. -Note that running the `db:migrate` also invokes the `db:schema:dump` task, which +Note that running the `db:migrate` task also invokes the `db:schema:dump` task, which will update your `db/schema.rb` file to match the structure of your database. If you specify a target version, Active Record will run the required migrations @@ -651,7 +715,7 @@ is the numerical prefix on the migration's filename. For example, to migrate to version 20080906120000 run: ```bash -$ rake db:migrate VERSION=20080906120000 +$ bin/rake db:migrate VERSION=20080906120000 ``` If version 20080906120000 is greater than the current version (i.e., it is @@ -668,7 +732,7 @@ mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run: ```bash -$ rake db:rollback +$ bin/rake db:rollback ``` This will rollback the latest migration, either by reverting the `change` @@ -676,7 +740,7 @@ method or by running the `down` method. If you need to undo several migrations you can provide a `STEP` parameter: ```bash -$ rake db:rollback STEP=3 +$ bin/rake db:rollback STEP=3 ``` will revert the last 3 migrations. @@ -686,7 +750,7 @@ back up again. As with the `db:rollback` task, you can use the `STEP` parameter if you need to go more than one version back, for example: ```bash -$ rake db:migrate:redo STEP=3 +$ bin/rake db:migrate:redo STEP=3 ``` Neither of these Rake tasks do anything you could not do with `db:migrate`. They @@ -716,7 +780,7 @@ the corresponding migration will have its `change`, `up` or `down` method invoked, for example: ```bash -$ rake db:migrate:up VERSION=20080906120000 +$ bin/rake db:migrate:up VERSION=20080906120000 ``` will run the 20080906120000 migration by running the `change` method (or the @@ -732,7 +796,7 @@ To run migrations against another environment you can specify it using the migrations against the `test` environment you could run: ```bash -$ rake db:migrate RAILS_ENV=test +$ bin/rake db:migrate RAILS_ENV=test ``` ### Changing the Output of Running Migrations @@ -764,7 +828,7 @@ class CreateProducts < ActiveRecord::Migration create_table :products do |t| t.string :name t.text :description - t.timestamps + t.timestamps null: false end end @@ -818,159 +882,6 @@ The `revert` method can be helpful when writing a new migration to undo previous migrations in whole or in part (see [Reverting Previous Migrations](#reverting-previous-migrations) above). -Using Models in Your Migrations -------------------------------- - -When creating or updating data in a migration it is often tempting to use one -of your models. After all, they exist to provide easy access to the underlying -data. This can be done, but some caution should be observed. - -For example, problems occur when the model uses database columns which are (1) -not currently in the database and (2) will be created by this or a subsequent -migration. - -Consider this example, where Alice and Bob are working on the same code base -which contains a `Product` model: - -Bob goes on vacation. - -Alice creates a migration for the `products` table which adds a new column and -initializes it: - -```ruby -# db/migrate/20100513121110_add_flag_to_product.rb - -class AddFlagToProduct < ActiveRecord::Migration - def change - add_column :products, :flag, :boolean - reversible do |dir| - dir.up { Product.update_all flag: false } - end - end -end -``` - -She also adds a validation to the `Product` model for the new column: - -```ruby -# app/models/product.rb - -class Product < ActiveRecord::Base - validates :flag, inclusion: { in: [true, false] } -end -``` - -Alice adds a second migration which adds another column to the `products` -table and initializes it: - -```ruby -# db/migrate/20100515121110_add_fuzz_to_product.rb - -class AddFuzzToProduct < ActiveRecord::Migration - def change - add_column :products, :fuzz, :string - reversible do |dir| - dir.up { Product.update_all fuzz: 'fuzzy' } - end - end -end -``` - -She also adds a validation to the `Product` model for the new column: - -```ruby -# app/models/product.rb - -class Product < ActiveRecord::Base - validates :flag, inclusion: { in: [true, false] } - validates :fuzz, presence: true -end -``` - -Both migrations work for Alice. - -Bob comes back from vacation and: - -* Updates the source - which contains both migrations and the latest version - of the Product model. -* Runs outstanding migrations with `rake db:migrate`, which - includes the one that updates the `Product` model. - -The migration crashes because when the model attempts to save, it tries to -validate the second added column, which is not in the database when the _first_ -migration runs: - -``` -rake aborted! -An error has occurred, this and all later migrations canceled: - -undefined method `fuzz' for #<Product:0x000001049b14a0> -``` - -A fix for this is to create a local model within the migration. This keeps -Rails from running the validations, so that the migrations run to completion. - -When using a local model, it's a good idea to call -`Product.reset_column_information` to refresh the Active Record cache for the -`Product` model prior to updating data in the database. - -If Alice had done this instead, there would have been no problem: - -```ruby -# db/migrate/20100513121110_add_flag_to_product.rb - -class AddFlagToProduct < ActiveRecord::Migration - class Product < ActiveRecord::Base - end - - def change - add_column :products, :flag, :boolean - Product.reset_column_information - reversible do |dir| - dir.up { Product.update_all flag: false } - end - end -end -``` - -```ruby -# db/migrate/20100515121110_add_fuzz_to_product.rb - -class AddFuzzToProduct < ActiveRecord::Migration - class Product < ActiveRecord::Base - end - - def change - add_column :products, :fuzz, :string - Product.reset_column_information - reversible do |dir| - dir.up { Product.update_all fuzz: 'fuzzy' } - end - end -end -``` - -There are other ways in which the above example could have gone badly. - -For example, imagine that Alice creates a migration that selectively -updates the `description` field on certain products. She runs the -migration, commits the code, and then begins working on the next feature, -which is to add a new column `fuzz` to the products table. - -She creates two migrations for this new feature, one which adds the new -column, and a second which selectively updates the `fuzz` column based on -other product attributes. - -These migrations run just fine, but when Bob comes back from his vacation -and calls `rake db:migrate` to run all the outstanding migrations, he gets a -subtle bug: The descriptions have defaults, and the `fuzz` column is present, -but `fuzz` is `nil` on all products. - -The solution is again to use `Product.reset_column_information` before -referencing the Product model in a migration, ensuring the Active Record's -knowledge of the table structure is current before manipulating data in those -records. - Schema Dumping and You ---------------------- @@ -1032,10 +943,10 @@ 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 foreign key constraints, triggers, or stored procedures. 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`. +items such as triggers, or stored procedures. 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`. 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` @@ -1053,11 +964,16 @@ schema into a RDBMS other than the one used to create it. Because schema dumps are the authoritative source for your database schema, it is strongly recommended that you check them into source control. +`db/schema.rb` contains the current version number of the database. This +ensures conflicts are going to happen in the case of a merge where both +branches touched the schema. When that happens, solve conflicts manually, +keeping the highest version number of the two. + Active Record and Referential Integrity --------------------------------------- The Active Record way claims that intelligence belongs in your models, not in -the database. As such, features such as triggers or foreign key constraints, +the database. As such, features such as triggers or constraints, which push some of that intelligence back into the database, are not heavily used. @@ -1066,14 +982,10 @@ which models can enforce data integrity. The `:dependent` option on associations allows models to automatically destroy child objects when the parent is destroyed. Like anything which operates at the application level, these cannot guarantee referential integrity and so some people augment them -with foreign key constraints in the database. - -Although Active Record does not provide any tools for working directly with -such features, the `execute` method can be used to execute arbitrary SQL. You -can also use a gem like -[foreigner](https://github.com/matthuhiggins/foreigner) which adds foreign key -support to Active Record (including support for dumping foreign keys in -`db/schema.rb`). +with [foreign key constraints](#foreign-keys) in the database. + +Although Active Record does not provide all the tools for working directly with +such features, the `execute` method can be used to execute arbitrary SQL. Migrations and Seed Data ------------------------ diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md new file mode 100644 index 0000000000..4d9c1776f4 --- /dev/null +++ b/guides/source/active_record_postgresql.md @@ -0,0 +1,438 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Active Record and PostgreSQL +============================ + +This guide covers PostgreSQL specific usage of Active Record. + +After reading this guide, you will know: + +* How to use PostgreSQL's datatypes. +* How to use UUID primary keys. +* How to implement full text search with PostgreSQL. +* How to back your Active Record models with database views. + +-------------------------------------------------------------------------------- + +In order to use the PostgreSQL adapter you need to have at least version 8.2 +installed. Older versions are not supported. + +To get started with PostgreSQL have a look at the +[configuring Rails guide](configuring.html#configuring-a-postgresql-database). +It describes how to properly setup Active Record for PostgreSQL. + +Datatypes +--------- + +PostgreSQL offers a number of specific datatypes. Following is a list of types, +that are supported by the PostgreSQL adapter. + +### Bytea + +* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-binary.html) +* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-binarystring.html) + +```ruby +# db/migrate/20140207133952_create_documents.rb +create_table :documents do |t| + t.binary 'payload' +end + +# app/models/document.rb +class Document < ActiveRecord::Base +end + +# Usage +data = File.read(Rails.root + "tmp/output.pdf") +Document.create payload: data +``` + +### Array + +* [type definition](http://www.postgresql.org/docs/9.3/static/arrays.html) +* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-array.html) + +```ruby +# db/migrate/20140207133952_create_books.rb +create_table :books do |t| + t.string 'title' + t.string 'tags', array: true + t.integer 'ratings', array: true +end +add_index :books, :tags, using: 'gin' +add_index :books, :ratings, using: 'gin' + +# app/models/book.rb +class Book < ActiveRecord::Base +end + +# Usage +Book.create title: "Brave New World", + tags: ["fantasy", "fiction"], + ratings: [4, 5] + +## Books for a single tag +Book.where("'fantasy' = ANY (tags)") + +## Books for multiple tags +Book.where("tags @> ARRAY[?]::varchar[]", ["fantasy", "fiction"]) + +## Books with 3 or more ratings +Book.where("array_length(ratings, 1) >= 3") +``` + +### Hstore + +* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html) + +NOTE: you need to enable the `hstore` extension to use hstore. + +```ruby +# db/migrate/20131009135255_create_profiles.rb +ActiveRecord::Schema.define do + enable_extension 'hstore' unless extension_enabled?('hstore') + create_table :profiles do |t| + t.hstore 'settings' + end +end + +# app/models/profile.rb +class Profile < ActiveRecord::Base +end + +# Usage +Profile.create(settings: { "color" => "blue", "resolution" => "800x600" }) + +profile = Profile.first +profile.settings # => {"color"=>"blue", "resolution"=>"800x600"} + +profile.settings = {"color" => "yellow", "resolution" => "1280x1024"} +profile.save! +``` + +### JSON + +* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-json.html) +* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-json.html) + +```ruby +# db/migrate/20131220144913_create_events.rb +create_table :events do |t| + t.json 'payload' +end + +# app/models/event.rb +class Event < ActiveRecord::Base +end + +# Usage +Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]}) + +event = Event.first +event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]} + +## Query based on JSON document +# The -> operator returns the original JSON type (which might be an object), whereas ->> returns text +Event.where("payload->>'kind' = ?", "user_renamed") +``` + +### Range Types + +* [type definition](http://www.postgresql.org/docs/9.3/static/rangetypes.html) +* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-range.html) + +This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.1.1/Range.html) objects. + +```ruby +# db/migrate/20130923065404_create_events.rb +create_table :events do |t| + t.daterange 'duration' +end + +# app/models/event.rb +class Event < ActiveRecord::Base +end + +# Usage +Event.create(duration: Date.new(2014, 2, 11)..Date.new(2014, 2, 12)) + +event = Event.first +event.duration # => Tue, 11 Feb 2014...Thu, 13 Feb 2014 + +## All Events on a given date +Event.where("duration @> ?::date", Date.new(2014, 2, 12)) + +## Working with range bounds +event = Event. + select("lower(duration) AS starts_at"). + select("upper(duration) AS ends_at").first + +event.starts_at # => Tue, 11 Feb 2014 +event.ends_at # => Thu, 13 Feb 2014 +``` + +### Composite Types + +* [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html) + +Currently there is no special support for composite types. They are mapped to +normal text columns: + +```sql +CREATE TYPE full_address AS +( + city VARCHAR(90), + street VARCHAR(90) +); +``` + +```ruby +# db/migrate/20140207133952_create_contacts.rb +execute <<-SQL + CREATE TYPE full_address AS + ( + city VARCHAR(90), + street VARCHAR(90) + ); +SQL +create_table :contacts do |t| + t.column :address, :full_address +end + +# app/models/contact.rb +class Contact < ActiveRecord::Base +end + +# Usage +Contact.create address: "(Paris,Champs-Élysées)" +contact = Contact.first +contact.address # => "(Paris,Champs-Élysées)" +contact.address = "(Paris,Rue Basse)" +contact.save! +``` + +### Enumerated Types + +* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-enum.html) + +Currently there is no special support for enumerated types. They are mapped as +normal text columns: + +```ruby +# db/migrate/20131220144913_create_articles.rb +execute <<-SQL + CREATE TYPE article_status AS ENUM ('draft', 'published'); +SQL +create_table :articles do |t| + t.column :status, :article_status +end + +# app/models/article.rb +class Article < ActiveRecord::Base +end + +# Usage +Article.create status: "draft" +article = Article.first +article.status # => "draft" + +article.status = "published" +article.save! +``` + +### UUID + +* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html) +* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html) + + +```ruby +# db/migrate/20131220144913_create_revisions.rb +create_table :revisions do |t| + t.column :identifier, :uuid +end + +# app/models/revision.rb +class Revision < ActiveRecord::Base +end + +# Usage +Revision.create identifier: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11" + +revision = Revision.first +revision.identifier # => "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" +``` + +### Bit String Types + +* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-bit.html) +* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-bitstring.html) + +```ruby +# db/migrate/20131220144913_create_users.rb +create_table :users, force: true do |t| + t.column :settings, "bit(8)" +end + +# app/models/device.rb +class User < ActiveRecord::Base +end + +# Usage +User.create settings: "01010011" +user = User.first +user.settings # => "01010011" +user.settings = "0xAF" +user.settings # => 10101111 +user.save! +``` + +### Network Address Types + +* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html) + +The types `inet` and `cidr` are mapped to Ruby +[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html) +objects. The `macaddr` type is mapped to normal text. + +```ruby +# db/migrate/20140508144913_create_devices.rb +create_table(:devices, force: true) do |t| + t.inet 'ip' + t.cidr 'network' + t.macaddr 'address' +end + +# app/models/device.rb +class Device < ActiveRecord::Base +end + +# Usage +macbook = Device.create(ip: "192.168.1.12", + network: "192.168.2.0/24", + address: "32:01:16:6d:05:ef") + +macbook.ip +# => #<IPAddr: IPv4:192.168.1.12/255.255.255.255> + +macbook.network +# => #<IPAddr: IPv4:192.168.2.0/255.255.255.0> + +macbook.address +# => "32:01:16:6d:05:ef" +``` + +### Geometric Types + +* [type definition](http://www.postgresql.org/docs/9.3/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. + + +UUID Primary Keys +----------------- + +NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs. + +```ruby +# db/migrate/20131220144913_create_devices.rb +enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp') +create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t| + t.string :kind +end + +# app/models/device.rb +class Device < ActiveRecord::Base +end + +# Usage +device = Device.create +device.id # => "814865cd-5a1d-4771-9306-4268f188fe9e" +``` + +Full Text Search +---------------- + +```ruby +# db/migrate/20131220144913_create_documents.rb +create_table :documents do |t| + t.string 'title' + t.string 'body' +end + +execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));" + +# app/models/document.rb +class Document < ActiveRecord::Base +end + +# Usage +Document.create(title: "Cats and Dogs", body: "are nice!") + +## all documents matching 'cat & dog' +Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)", + "cat & dog") +``` + +Database Views +-------------- + +* [view creation](http://www.postgresql.org/docs/9.3/static/sql-createview.html) + +Imagine you need to work with a legacy database containing the following table: + +``` +rails_pg_guide=# \d "TBL_ART" + Table "public.TBL_ART" + Column | Type | Modifiers +------------+-----------------------------+------------------------------------------------------------ + INT_ID | integer | not null default nextval('"TBL_ART_INT_ID_seq"'::regclass) + STR_TITLE | character varying | + STR_STAT | character varying | default 'draft'::character varying + DT_PUBL_AT | timestamp without time zone | + BL_ARCH | boolean | default false +Indexes: + "TBL_ART_pkey" PRIMARY KEY, btree ("INT_ID") +``` + +This table does not follow the Rails conventions at all. +Because simple PostgreSQL views are updateable by default, +we can wrap it as follows: + +```ruby +# db/migrate/20131220144913_create_articles_view.rb +execute <<-SQL +CREATE VIEW articles AS + SELECT "INT_ID" AS id, + "STR_TITLE" AS title, + "STR_STAT" AS status, + "DT_PUBL_AT" AS published_at, + "BL_ARCH" AS archived + FROM "TBL_ART" + WHERE "BL_ARCH" = 'f' + SQL + +# app/models/article.rb +class Article < ActiveRecord::Base + self.primary_key = "id" + def archive! + update_attribute :archived, true + end +end + +# Usage +first = Article.create! title: "Winter is coming", + status: "published", + published_at: 1.year.ago +second = Article.create! title: "Brace yourself", + status: "draft", + published_at: 1.month.ago + +Article.count # => 1 +first.archive! +Article.count # => 2 +``` + +NOTE: This application only cares about non-archived `Articles`. A view also +allows for conditions so we can exclude the archived `Articles` directly. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 4725e2c8a2..434b308170 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Query Interface ============================= @@ -9,6 +11,7 @@ After reading this guide, you will know: * How to specify the order, retrieved attributes, grouping, and other properties of the found records. * How to use eager loading to reduce the number of database queries needed for data retrieval. * How to use dynamic finders methods. +* How to use method chaining to use multiple ActiveRecord methods together. * How to check for the existence of particular records. * How to perform various calculations on Active Record models. * How to run EXPLAIN on relations. @@ -87,15 +90,15 @@ The primary operation of `Model.find(options)` can be summarized as: * Convert the supplied options to an equivalent SQL query. * Fire the SQL query and retrieve the corresponding results from the database. * Instantiate the equivalent Ruby object of the appropriate model for every resulting row. -* Run `after_find` callbacks, if any. +* Run `after_find` and then `after_initialize` callbacks, if any. ### Retrieving a Single Object Active Record provides several different ways of retrieving a single object. -#### Using a Primary Key +#### `find` -Using `Model.find(primary_key)`, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example: +Using the `find` method, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example: ```ruby # Find the client with primary key (id) 10. @@ -109,119 +112,103 @@ The SQL equivalent of the above is: SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1 ``` -`Model.find(primary_key)` will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found. - -#### `take` +The `find` method will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found. -`Model.take` retrieves a record without any implicit ordering. For example: +You can also use this method to query for multiple objects. Call the `find` method and pass in an array of primary keys. The return will be an array containing all of the matching records for the supplied _primary keys_. For example: ```ruby -client = Client.take -# => #<Client id: 1, first_name: "Lifo"> +# Find the clients with primary keys 1 and 10. +client = Client.find([1, 10]) # Or even Client.find(1, 10) +# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients LIMIT 1 +SELECT * FROM clients WHERE (clients.id IN (1,10)) ``` -`Model.take` returns `nil` if no record is found and no exception will be raised. +WARNING: The `find` method will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys. -TIP: The retrieved record may vary depending on the database engine. - -#### `first` +#### `take` -`Model.first` finds the first record ordered by the primary key. For example: +The `take` method retrieves a record without any implicit ordering. For example: ```ruby -client = Client.first +client = Client.take # => #<Client id: 1, first_name: "Lifo"> ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 +SELECT * FROM clients LIMIT 1 ``` -`Model.first` returns `nil` if no matching record is found and no exception will be raised. - -#### `last` +The `take` method returns `nil` if no record is found and no exception will be raised. -`Model.last` finds the last record ordered by the primary key. For example: +You can pass in a numerical argument to the `take` method to return up to that number of results. For example ```ruby -client = Client.last -# => #<Client id: 221, first_name: "Russel"> +client = Client.take(2) +# => [ + #<Client id: 1, first_name: "Lifo">, + #<Client id: 220, first_name: "Sara"> +] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 -``` - -`Model.last` returns `nil` if no matching record is found and no exception will be raised. - -#### `find_by` - -`Model.find_by` finds the first record matching some conditions. For example: - -```ruby -Client.find_by first_name: 'Lifo' -# => #<Client id: 1, first_name: "Lifo"> - -Client.find_by first_name: 'Jon' -# => nil +SELECT * FROM clients LIMIT 2 ``` -It is equivalent to writing: +The `take!` method behaves exactly like `take`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. -```ruby -Client.where(first_name: 'Lifo').take -``` +TIP: The retrieved record may vary depending on the database engine. -#### `take!` +#### `first` -`Model.take!` retrieves a record without any implicit ordering. For example: +The `first` method finds the first record ordered by the primary key. For example: ```ruby -client = Client.take! +client = Client.first # => #<Client id: 1, first_name: "Lifo"> ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients LIMIT 1 +SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 ``` -`Model.take!` raises `ActiveRecord::RecordNotFound` if no matching record is found. - -#### `first!` +The `first` method returns `nil` if no matching record is found and no exception will be raised. -`Model.first!` finds the first record ordered by the primary key. For example: +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! -# => #<Client id: 1, first_name: "Lifo"> +client = Client.first(3) +# => [ + #<Client id: 1, first_name: "Lifo">, + #<Client id: 2, first_name: "Fifo">, + #<Client id: 3, first_name: "Filo"> +] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 +SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3 ``` -`Model.first!` raises `ActiveRecord::RecordNotFound` if no matching record is found. +The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. -#### `last!` +#### `last` -`Model.last!` finds the last record ordered by the primary key. For example: +The `last` method finds the last record ordered by the primary key. For example: ```ruby -client = Client.last! +client = Client.last # => #<Client id: 221, first_name: "Russel"> ``` @@ -231,92 +218,62 @@ The SQL equivalent of the above is: SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 ``` -`Model.last!` raises `ActiveRecord::RecordNotFound` if no matching record is found. - -#### `find_by!` - -`Model.find_by!` finds the first record matching some conditions. It raises `ActiveRecord::RecordNotFound` if no matching record is found. For example: - -```ruby -Client.find_by! first_name: 'Lifo' -# => #<Client id: 1, first_name: "Lifo"> - -Client.find_by! first_name: 'Jon' -# => ActiveRecord::RecordNotFound -``` - -It is equivalent to writing: - -```ruby -Client.where(first_name: 'Lifo').take! -``` - -### Retrieving Multiple Objects +The `last` method returns `nil` if no matching record is found and no exception will be raised. -#### Using Multiple Primary Keys - -`Model.find(array_of_primary_key)` accepts an array of _primary keys_, returning an array containing all of the matching records for the supplied _primary keys_. For example: +You can pass in a numerical argument to the `last` method to return up to that number of results. For example ```ruby -# Find the clients with primary keys 1 and 10. -client = Client.find([1, 10]) # Or even Client.find(1, 10) -# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">] +client = Client.last(3) +# => [ + #<Client id: 219, first_name: "James">, + #<Client id: 220, first_name: "Sara">, + #<Client id: 221, first_name: "Russel"> +] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients WHERE (clients.id IN (1,10)) +SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3 ``` -WARNING: `Model.find(array_of_primary_key)` will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys. +The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. -#### take +#### `find_by` -`Model.take(limit)` retrieves the first number of records specified by `limit` without any explicit ordering: +The `find_by` method finds the first record matching some conditions. For example: ```ruby -Client.take(2) -# => [#<Client id: 1, first_name: "Lifo">, - #<Client id: 2, first_name: "Raf">] -``` - -The SQL equivalent of the above is: +Client.find_by first_name: 'Lifo' +# => #<Client id: 1, first_name: "Lifo"> -```sql -SELECT * FROM clients LIMIT 2 +Client.find_by first_name: 'Jon' +# => nil ``` -#### first - -`Model.first(limit)` finds the first number of records specified by `limit` ordered by primary key: +It is equivalent to writing: ```ruby -Client.first(2) -# => [#<Client id: 1, first_name: "Lifo">, - #<Client id: 2, first_name: "Raf">] +Client.where(first_name: 'Lifo').take ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY id ASC LIMIT 2 +SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1 ``` -#### last - -`Model.last(limit)` finds the number of records specified by `limit` ordered by primary key in descending order: +The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example: ```ruby -Client.last(2) -# => [#<Client id: 10, first_name: "Ryan">, - #<Client id: 9, first_name: "John">] +Client.find_by! first_name: 'does not exist' +# => ActiveRecord::RecordNotFound ``` -The SQL equivalent of the above is: +This is equivalent to writing: -```sql -SELECT * FROM clients ORDER BY id DESC LIMIT 2 +```ruby +Client.where(first_name: 'does not exist').take! ``` ### Retrieving Multiple Objects in Batches @@ -328,7 +285,7 @@ This may appear straightforward: ```ruby # This is very inefficient when the users table has thousands of rows. User.all.each do |user| - NewsLetter.weekly_deliver(user) + NewsMailer.weekly(user).deliver_now end ``` @@ -344,7 +301,15 @@ The `find_each` method retrieves a batch of records and then yields _each_ recor ```ruby User.find_each do |user| - NewsLetter.weekly_deliver(user) + NewsMailer.weekly(user).deliver_now +end +``` + +To add conditions to a `find_each` operation you can chain other Active Record methods such as `where`: + +```ruby +User.where(weekly_subscriber: true).find_each do |user| + NewsMailer.weekly(user).deliver_now end ``` @@ -360,7 +325,7 @@ The `:batch_size` option allows you to specify the number of records to be retri ```ruby User.find_each(batch_size: 5000) do |user| - NewsLetter.weekly_deliver(user) + NewsMailer.weekly(user).deliver_now end ``` @@ -372,7 +337,7 @@ For example, to send newsletters only to users with the primary key starting fro ```ruby User.find_each(start: 2000, batch_size: 5000) do |user| - NewsLetter.weekly_deliver(user) + NewsMailer.weekly(user).deliver_now end ``` @@ -384,16 +349,14 @@ The `find_in_batches` method is similar to `find_each`, since both retrieve batc ```ruby # Give add_invoices an array of 1000 invoices at a time -Invoice.find_in_batches(include: :invoice_lines) do |invoices| +Invoice.find_in_batches do |invoices| export.add_invoices(invoices) end ``` -NOTE: The `:include` option allows you to name associations that should be loaded alongside with the models. - ##### Options for `find_in_batches` -The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`, as well as most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_in_batches`. +The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`. Conditions ---------- @@ -472,8 +435,8 @@ Client.where('locked' => true) In the case of a belongs_to relationship, an association key can be used to specify the model if an Active Record object is used as the value. This method works with polymorphic relationships as well. ```ruby -Post.where(author: author) -Author.joins(:posts).where(posts: { author: author }) +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)`. @@ -511,7 +474,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) `NOT` SQL queries can be built by `where.not`. ```ruby -Post.where.not(author: author) +Article.where.not(author: author) ``` In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. @@ -659,6 +622,23 @@ FROM orders GROUP BY date(created_at) ``` +### Total of grouped items + +To get the total of grouped items on a single query call `count` after the `group`. + +```ruby +Order.group(:status).count +# => { 'awaiting_approval' => 7, 'paid' => 12 } +``` + +The SQL that would be executed would be something like this: + +```sql +SELECT COUNT (*) AS count_all, status AS status +FROM "orders" +GROUP BY status +``` + Having ------ @@ -690,32 +670,32 @@ Overriding Conditions You can specify certain conditions to be removed using the `unscope` method. For example: ```ruby -Post.where('id > 10').limit(20).order('id asc').except(:order) +Article.where('id > 10').limit(20).order('id asc').unscope(:order) ``` The SQL that would be executed: ```sql -SELECT * FROM posts WHERE id > 10 LIMIT 20 +SELECT * FROM articles WHERE id > 10 LIMIT 20 # Original query without `unscope` -SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 +SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20 ``` -You can additionally unscope specific where clauses. For example: +You can also unscope specific `where` clauses. For example: ```ruby -Post.where(id: 10, trashed: false).unscope(where: :id) -# => SELECT "posts".* FROM "posts" WHERE trashed = 0 +Article.where(id: 10, trashed: false).unscope(where: :id) +# SELECT "articles".* FROM "articles" WHERE trashed = 0 ``` A relation which has used `unscope` will affect any relation it is merged in to: ```ruby -Post.order('id asc').merge(Post.unscope(:order)) -# => SELECT "posts".* FROM "posts" +Article.order('id asc').merge(Article.unscope(:order)) +# SELECT "articles".* FROM "articles" ``` ### `only` @@ -723,16 +703,16 @@ Post.order('id asc').merge(Post.unscope(:order)) You can also override conditions using the `only` method. For example: ```ruby -Post.where('id > 10').limit(20).order('id desc').only(:order, :where) +Article.where('id > 10').limit(20).order('id desc').only(:order, :where) ``` The SQL that would be executed: ```sql -SELECT * FROM posts WHERE id > 10 ORDER BY id DESC +SELECT * FROM articles WHERE id > 10 ORDER BY id DESC # Original query without `only` -SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 +SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20 ``` @@ -741,25 +721,25 @@ SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 The `reorder` method overrides the default scope order. For example: ```ruby -class Post < ActiveRecord::Base - .. - .. +class Article < ActiveRecord::Base has_many :comments, -> { order('posted_at DESC') } end -Post.find(10).comments.reorder('name') +Article.find(10).comments.reorder('name') ``` The SQL that would be executed: ```sql -SELECT * FROM posts WHERE id = 10 ORDER BY name +SELECT * FROM articles WHERE id = 10 +SELECT * FROM comments WHERE article_id = 10 ORDER BY name ``` In case the `reorder` clause is not used, the SQL executed would be: ```sql -SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC +SELECT * FROM articles WHERE id = 10 +SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC ``` ### `reverse_order` @@ -795,25 +775,25 @@ This method accepts **no** arguments. The `rewhere` method overrides an existing, named where condition. For example: ```ruby -Post.where(trashed: true).rewhere(trashed: false) +Article.where(trashed: true).rewhere(trashed: false) ``` The SQL that would be executed: ```sql -SELECT * FROM posts WHERE `trashed` = 0 +SELECT * FROM articles WHERE `trashed` = 0 ``` In case the `rewhere` clause is not used, ```ruby -Post.where(trashed: true).where(trashed: false) +Article.where(trashed: true).where(trashed: false) ``` the SQL executed would be: ```sql -SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0 +SELECT * FROM articles WHERE `trashed` = 1 AND `trashed` = 0 ``` Null Relation @@ -822,21 +802,21 @@ Null Relation The `none` method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results. ```ruby -Post.none # returns an empty Relation and fires no queries. +Article.none # returns an empty Relation and fires no queries. ``` ```ruby -# The visible_posts method below is expected to return a Relation. -@posts = current_user.visible_posts.where(name: params[:name]) +# The visible_articles method below is expected to return a Relation. +@articles = current_user.visible_articles.where(name: params[:name]) -def visible_posts +def visible_articles case role when 'Country Manager' - Post.where(country: country) + Article.where(country: country) when 'Reviewer' - Post.published + Article.published when 'Bad User' - Post.none # => returning [] or nil breaks the caller code in this case + Article.none # => returning [] or nil breaks the caller code in this case end end ``` @@ -961,23 +941,23 @@ SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = WARNING: This method only works with `INNER JOIN`. -Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clause for those associations when using the `joins` method. +Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method. -For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models: +For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models: ```ruby class Category < ActiveRecord::Base - has_many :posts + has_many :articles end -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base belongs_to :category has_many :comments has_many :tags end class Comment < ActiveRecord::Base - belongs_to :post + belongs_to :article has_one :guest end @@ -986,7 +966,7 @@ class Guest < ActiveRecord::Base end class Tag < ActiveRecord::Base - belongs_to :post + belongs_to :article end ``` @@ -995,64 +975,64 @@ Now all of the following will produce the expected join queries using `INNER JOI #### Joining a Single Association ```ruby -Category.joins(:posts) +Category.joins(:articles) ``` This produces: ```sql SELECT categories.* FROM categories - INNER JOIN posts ON posts.category_id = categories.id + INNER JOIN articles ON articles.category_id = categories.id ``` -Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use `Category.joins(:posts).uniq`. +Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`. #### Joining Multiple Associations ```ruby -Post.joins(:category, :comments) +Article.joins(:category, :comments) ``` This produces: ```sql -SELECT posts.* FROM posts - INNER JOIN categories ON posts.category_id = categories.id - INNER JOIN comments ON comments.post_id = posts.id +SELECT articles.* FROM articles + INNER JOIN categories ON articles.category_id = categories.id + INNER JOIN comments ON comments.article_id = articles.id ``` -Or, in English: "return all posts that have a category and at least one comment". Note again that posts with multiple comments will show up multiple times. +Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times. #### Joining Nested Associations (Single Level) ```ruby -Post.joins(comments: :guest) +Article.joins(comments: :guest) ``` This produces: ```sql -SELECT posts.* FROM posts - INNER JOIN comments ON comments.post_id = posts.id +SELECT articles.* FROM articles + INNER JOIN comments ON comments.article_id = articles.id INNER JOIN guests ON guests.comment_id = comments.id ``` -Or, in English: "return all posts that have a comment made by a guest." +Or, in English: "return all articles that have a comment made by a guest." #### Joining Nested Associations (Multiple Level) ```ruby -Category.joins(posts: [{ comments: :guest }, :tags]) +Category.joins(articles: [{ comments: :guest }, :tags]) ``` This produces: ```sql SELECT categories.* FROM categories - INNER JOIN posts ON posts.category_id = categories.id - INNER JOIN comments ON comments.post_id = posts.id + INNER JOIN articles ON articles.category_id = categories.id + INNER JOIN comments ON comments.article_id = articles.id INNER JOIN guests ON guests.comment_id = comments.id - INNER JOIN tags ON tags.post_id = posts.id + INNER JOIN tags ON tags.article_id = articles.id ``` ### Specifying Conditions on the Joined Tables @@ -1121,18 +1101,18 @@ Active Record lets you eager load any number of associations with a single `Mode #### Array of Multiple Associations ```ruby -Post.includes(:category, :comments) +Article.includes(:category, :comments) ``` -This loads all the posts and the associated category and comments for each post. +This loads all the articles and the associated category and comments for each article. #### Nested Associations Hash ```ruby -Category.includes(posts: [{ comments: :guest }, :tags]).find(1) +Category.includes(articles: [{ comments: :guest }, :tags]).find(1) ``` -This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association. +This will find the category with id 1 and eager load all of the associated articles, the associated articles' tags and comments, and every comment's guest association. ### Specifying Conditions on Eager Loaded Associations @@ -1141,18 +1121,31 @@ Even though Active Record lets you specify conditions on the eager loaded associ However if you must do this, you may use `where` as you would normally. ```ruby -Post.includes(:comments).where("comments.visible" => true) +Article.includes(:comments).where(comments: { visible: true }) ``` -This would generate a query which contains a `LEFT OUTER JOIN` whereas the `joins` method would generate one using the `INNER JOIN` function instead. +This would generate a query which contains a `LEFT OUTER JOIN` whereas the +`joins` method would generate one using the `INNER JOIN` function instead. ```ruby - SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1) + SELECT "articles"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "articles" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" WHERE (comments.visible = 1) ``` If there was no `where` condition, this would generate the normal set of two queries. -If, in the case of this `includes` query, there were no comments for any posts, all the posts would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned. +NOTE: Using `where` like this will only work when you pass it a Hash. For +SQL-fragments you need use `references` to force joined tables: + +```ruby +Article.includes(:comments).where("comments.visible = true").references(:comments) +``` + +If, in the case of this `includes` query, there were no comments for any +articles, all the articles would still be loaded. By using `joins` (an INNER +JOIN), the join conditions **must** match, otherwise no records will be +returned. + + Scopes ------ @@ -1162,7 +1155,7 @@ Scoping allows you to specify commonly-used queries which can be referenced as m To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base scope :published, -> { where(published: true) } end ``` @@ -1170,7 +1163,7 @@ end This is exactly the same as defining a class method, and which you use is a matter of personal preference: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base def self.published where(published: true) end @@ -1180,7 +1173,7 @@ end Scopes are also chainable within scopes: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base scope :published, -> { where(published: true) } scope :published_and_commented, -> { published.where("comments_count > 0") } end @@ -1189,14 +1182,14 @@ end To call this `published` scope we can call it on either the class: ```ruby -Post.published # => [published posts] +Article.published # => [published articles] ``` -Or on an association consisting of `Post` objects: +Or on an association consisting of `Article` objects: ```ruby category = Category.first -category.posts.published # => [published posts belonging to this category] +category.articles.published # => [published articles belonging to this category] ``` ### Passing in arguments @@ -1204,7 +1197,7 @@ category.posts.published # => [published posts belonging to this category] Your scope can take arguments: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base scope :created_before, ->(time) { where("created_at < ?", time) } end ``` @@ -1212,13 +1205,13 @@ end Call the scope as if it were a class method: ```ruby -Post.created_before(Time.zone.now) +Article.created_before(Time.zone.now) ``` However, this is just duplicating the functionality that would be provided to you by a class method. ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base def self.created_before(time) where("created_at < ?", time) end @@ -1228,7 +1221,36 @@ end Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects: ```ruby -category.posts.created_before(time) +category.articles.created_before(time) +``` + +### Applying a default scope + +If we wish for a scope to be applied across all queries to the model we can use the +`default_scope` method within the model itself. + +```ruby +class Client < ActiveRecord::Base + default_scope { where("removed_at IS NULL") } +end +``` + +When queries are executed on this model, the SQL query will now look something like +this: + +```sql +SELECT * FROM clients WHERE removed_at IS NULL +``` + +If you need to do more complex things with a default scope, you can alternatively +define it as a class method: + +```ruby +class Client < ActiveRecord::Base + def self.default_scope + # Should return an ActiveRecord::Relation. + end +end ``` ### Merging of scopes @@ -1242,26 +1264,26 @@ class User < ActiveRecord::Base end User.active.inactive -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' ``` We can mix and match `scope` and `where` conditions and the final sql -will have all conditions joined with `AND` . +will have all conditions joined with `AND`. ```ruby User.active.where(state: 'finished') -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' ``` If we do want the `last where clause` to win then `Relation#merge` can -be used . +be used. ```ruby User.active.merge(User.inactive) -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' ``` -One important caveat is that `default_scope` will be overridden by +One important caveat is that `default_scope` will be prepended in `scope` and `where` conditions. ```ruby @@ -1272,48 +1294,18 @@ class User < ActiveRecord::Base end User.all -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' User.active -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' User.where(state: 'inactive') -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' ``` -As you can see above the `default_scope` is being overridden by both +As you can see above the `default_scope` is being merged in both `scope` and `where` conditions. - -### Applying a default scope - -If we wish for a scope to be applied across all queries to the model we can use the -`default_scope` method within the model itself. - -```ruby -class Client < ActiveRecord::Base - default_scope { where("removed_at IS NULL") } -end -``` - -When queries are executed on this model, the SQL query will now look something like -this: - -```sql -SELECT * FROM clients WHERE removed_at IS NULL -``` - -If you need to do more complex things with a default scope, you can alternatively -define it as a class method: - -```ruby -class Client < ActiveRecord::Base - def self.default_scope - # Should return an ActiveRecord::Relation. - end -end -``` - ### Removing All Scoping If we wish to remove scoping for any reason we can use the `unscoped` method. This is @@ -1338,17 +1330,69 @@ Client.unscoped { Dynamic Finders --------------- -NOTE: Dynamic finders have been deprecated in Rails 4.0 and will be -removed in Rails 4.1. The best practice is to use Active Record scopes -instead. You can find the deprecation gem at -https://github.com/rails/activerecord-deprecated_finders - For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods. You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")` If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`. +Understanding The Method Chaining +--------------------------------- + +The Active Record pattern implements [Method Chaining](http://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 +`ActiveRecord::Relation`, like `all`, `where`, and `joins`. Methods that return +a single object (see [Retrieving a Single Object Section](#retrieving-a-single-object)) +have to be at the end of the statement. + +There are some examples below. This guide won't cover all the possibilities, just a few as examples. +When an Active Record method is called, the query is not immediately generated and sent to the database, +this just happens when the data is actually needed. So each example below generates a single query. + +### Retrieving filtered data from multiple tables + +```ruby +Person + .select('people.id, people.name, comments.text') + .joins(:comments) + .where('comments.created_at > ?', 1.week.ago) +``` + +The result should be something like this: + +```sql +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' +``` + +### Retrieving specific data from multiple tables + +```ruby +Person + .select('people.id, people.name, companies.name') + .joins(:company) + .find_by('people.name' => 'John') # this should be the last +``` + +The above should generate: + +```sql +SELECT people.id, people.name, companies.name +FROM people +INNER JOIN companies + ON companies.person_id = people.id +WHERE people.name = 'John' +LIMIT 1 +``` + +NOTE: Remember that, if `find_by` returns more than one registry, it will take +just the first and ignore the others. Note the `LIMIT 1` statement above. + Find or Build a New Object -------------------------- @@ -1455,7 +1499,12 @@ If you'd like to use your own SQL to find records in a table you can use `find_b ```ruby Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id - ORDER clients.created_at desc") + ORDER BY clients.created_at desc") +# => [ + #<Client id: 1, first_name: "Lucas" >, + #<Client id: 2, first_name: "Jan" >, + # ... +] ``` `find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects. @@ -1465,12 +1514,16 @@ Client.find_by_sql("SELECT * FROM clients `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. ```ruby -Client.connection.select_all("SELECT * FROM clients WHERE id = '1'") +Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") +# => [ + {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, + {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} +] ``` ### `pluck` -`pluck` can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type. +`pluck` can be used to query single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type. ```ruby Client.where(active: true).pluck(:id) @@ -1592,20 +1645,20 @@ You can also use `any?` and `many?` to check for existence on a model or relatio ```ruby # via a model -Post.any? -Post.many? +Article.any? +Article.many? # via a named scope -Post.recent.any? -Post.recent.many? +Article.recent.any? +Article.recent.many? # via a relation -Post.where(published: true).any? -Post.where(published: true).many? +Article.where(published: true).any? +Article.where(published: true).many? # via an association -Post.first.categories.any? -Post.first.categories.many? +Article.first.categories.any? +Article.first.categories.many? ``` Calculations @@ -1695,19 +1748,26 @@ Running EXPLAIN You can run EXPLAIN on the queries triggered by relations. For example, ```ruby -User.where(id: 1).joins(:posts).explain +User.where(id: 1).joins(:articles).explain ``` may yield ``` -EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | -| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ +EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 ++----+-------------+----------+-------+---------------+ +| id | select_type | table | type | possible_keys | ++----+-------------+----------+-------+---------------+ +| 1 | SIMPLE | users | const | PRIMARY | +| 1 | SIMPLE | articles | ALL | NULL | ++----+-------------+----------+-------+---------------+ ++---------+---------+-------+------+-------------+ +| key | key_len | ref | rows | Extra | ++---------+---------+-------+------+-------------+ +| PRIMARY | 4 | const | 1 | | +| NULL | NULL | NULL | 1 | Using where | ++---------+---------+-------+------+-------------+ + 2 rows in set (0.00 sec) ``` @@ -1717,15 +1777,15 @@ Active Record performs a pretty printing that emulates the one of the database shells. So, the same query running with the PostgreSQL adapter would yield instead ``` -EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1 +EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1 QUERY PLAN ------------------------------------------------------------------------------ Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) - Join Filter: (posts.user_id = users.id) + Join Filter: (articles.user_id = users.id) -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) Index Cond: (id = 1) - -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) - Filter: (posts.user_id = 1) + -> Seq Scan on articles (cost=0.00..28.88 rows=8 width=4) + Filter: (articles.user_id = 1) (6 rows) ``` @@ -1734,26 +1794,39 @@ may need the results of previous ones. Because of that, `explain` actually executes the query, and then asks for the query plans. For example, ```ruby -User.where(id: 1).includes(:posts).explain +User.where(id: 1).includes(:articles).explain ``` yields ``` EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ ++----+-------------+-------+-------+---------------+ +| id | select_type | table | type | possible_keys | ++----+-------------+-------+-------+---------------+ +| 1 | SIMPLE | users | const | PRIMARY | ++----+-------------+-------+-------+---------------+ ++---------+---------+-------+------+-------+ +| key | key_len | ref | rows | Extra | ++---------+---------+-------+------+-------+ +| PRIMARY | 4 | const | 1 | | ++---------+---------+-------+------+-------+ + 1 row in set (0.00 sec) -EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1) -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ +EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN (1) ++----+-------------+----------+------+---------------+ +| id | select_type | table | type | possible_keys | ++----+-------------+----------+------+---------------+ +| 1 | SIMPLE | articles | ALL | NULL | ++----+-------------+----------+------+---------------+ ++------+---------+------+------+-------------+ +| key | key_len | ref | rows | Extra | ++------+---------+------+------+-------------+ +| NULL | NULL | NULL | 1 | Using where | ++------+---------+------+------+-------------+ + + 1 row in set (0.00 sec) ``` diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index efa826e8df..c9af70934a 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Validations ========================= @@ -85,7 +87,7 @@ end We can see how it works by looking at some `rails console` output: ```ruby -$ rails console +$ bin/rails console >> p = Person.new(name: "John Doe") => #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil> >> p.new_record? @@ -361,6 +363,8 @@ class Product < ActiveRecord::Base end ``` +Alternatively, you can require that the specified attribute does _not_ match the regular expression by using the `:without` option. + The default error message is _"is invalid"_. ### `inclusion` @@ -425,7 +429,7 @@ class Essay < ActiveRecord::Base validates :content, length: { minimum: 300, maximum: 400, - tokenizer: lambda { |str| str.scan(/\w+/) }, + tokenizer: lambda { |str| str.split(/\s+/) }, too_short: "must have at least %{count} words", too_long: "must have at most %{count} words" } @@ -485,6 +489,8 @@ constraints to acceptable values: * `:even` - Specifies the value must be an even number if set to true. The default error message for this option is _"must be even"_. +NOTE: By default, `numericality` doesn't allow `nil` values. You can use `allow_nil: true` option to permit it. + The default error message is _"is not a number"_. ### `presence` @@ -524,9 +530,16 @@ If you validate the presence of an object associated via a `has_one` or `marked_for_destruction?`. Since `false.blank?` is true, if you want to validate the presence of a boolean -field you should use `validates :field_name, inclusion: { in: [true, false] }`. +field you should use one of the following validations: + +```ruby +validates :boolean_field_name, presence: true +validates :boolean_field_name, inclusion: { in: [true, false] } +validates :boolean_field_name, exclusion: { in: [nil] } +``` -The default error message is _"can't be blank"_. +By using one of these validations, you will ensure the value will NOT be `nil` +which would result in a `NULL` value in most cases. ### `absence` @@ -575,7 +588,9 @@ This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, -you must create a unique index in your database. +you must create a unique index on both columns in your database. See +[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) +for more details about multiple column indexes. ```ruby class Account < ActiveRecord::Base @@ -616,10 +631,6 @@ The default error message is _"has already been taken"_. This helper passes the record to a separate class for validation. ```ruby -class Person < ActiveRecord::Base - validates_with GoodnessValidator -end - class GoodnessValidator < ActiveModel::Validator def validate(record) if record.first_name == "Evil" @@ -627,6 +638,10 @@ class GoodnessValidator < ActiveModel::Validator end end end + +class Person < ActiveRecord::Base + validates_with GoodnessValidator +end ``` NOTE: Errors added to `record.errors[:base]` relate to the state of the record @@ -644,10 +659,6 @@ Like all other validations, `validates_with` takes the `:if`, `:unless` and validator class as `options`: ```ruby -class Person < ActiveRecord::Base - validates_with GoodnessValidator, fields: [:first_name, :last_name] -end - class GoodnessValidator < ActiveModel::Validator def validate(record) if options[:fields].any?{|field| record.send(field) == "Evil" } @@ -655,6 +666,10 @@ class GoodnessValidator < ActiveModel::Validator end end end + +class Person < ActiveRecord::Base + validates_with GoodnessValidator, fields: [:first_name, :last_name] +end ``` Note that the validator will be initialized *only once* for the whole application @@ -696,7 +711,7 @@ we don't want names and surnames to begin with lower case. ```ruby class Person < ActiveRecord::Base validates_each :name, :surname do |record, attr, value| - record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ + record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/ end end ``` @@ -869,7 +884,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and ```ruby class Computer < ActiveRecord::Base validates :mouse, presence: true, - if: ["market.retail?", :desktop?] + if: ["market.retail?", :desktop?], unless: Proc.new { |c| c.trackpad.present? } end ``` @@ -908,8 +923,8 @@ end The easiest way to add custom validators for validating individual attributes is with the convenient `ActiveModel::EachValidator`. In this case, the custom validator class must implement a `validate_each` method which takes three -arguments: record, attribute and value which correspond to the instance, the -attribute to be validated and the value of the attribute in the passed +arguments: record, attribute, and value. These correspond to the instance, the +attribute to be validated, and the value of the attribute in the passed instance. ```ruby @@ -1127,15 +1142,15 @@ generating a scaffold, Rails will put some ERB into the `_form.html.erb` that it generates that displays the full list of errors on that model. Assuming we have a model that's been saved in an instance variable named -`@post`, it looks like this: +`@article`, it looks like this: ```ruby -<% if @post.errors.any? %> +<% if @article.errors.any? %> <div id="error_explanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> + <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2> <ul> - <% @post.errors.full_messages.each do |msg| %> + <% @article.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> @@ -1149,7 +1164,7 @@ the entry. ``` <div class="field_with_errors"> - <input id="post_title" name="post[title]" size="30" type="text" value=""> + <input id="article_title" name="article[title]" size="30" type="text" value=""> </div> ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 59dfefd22f..080cc41e87 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Support Core Extensions ============================== @@ -157,12 +159,12 @@ Active Support provides `duplicable?` to programmatically query an object about ```ruby "foo".duplicable? # => true -"".duplicable? # => true +"".duplicable? # => true 0.0.duplicable? # => false -false.duplicable? # => false +false.duplicable? # => false ``` -By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, and module objects. +By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, module, and method objects. WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. @@ -572,12 +574,12 @@ NOTE: Defined in `active_support/core_ext/module/aliasing.rb`. #### `alias_attribute` -Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (my mnemonic is they go in the same order as if you did an assignment): +Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (one mnemonic is that they go in the same order as if you did an assignment): ```ruby class User < ActiveRecord::Base - # let me refer to the email column as "login", - # possibly meaningful for authentication code + # You can refer to the email column as "login". + # This can be meaningful for authentication code. alias_attribute :login, :email end ``` @@ -761,7 +763,7 @@ Arguments may be bare constant names: Math.qualified_const_get("E") # => 2.718281828459045 ``` -These methods are analogous to their builtin counterparts. In particular, +These methods are analogous to their built-in counterparts. In particular, `qualified_constant_defined?` accepts an optional second argument to be able to say whether you want the predicate to look in the ancestors. This flag is taken into account for each constant in the expression while @@ -792,7 +794,7 @@ N.qualified_const_defined?("C::X") # => true As the last example implies, the second argument defaults to true, as in `const_defined?`. -For coherence with the builtin methods only relative paths are accepted. +For coherence with the built-in methods only relative paths are accepted. Absolute qualified constant names like `::Math::PI` raise `NameError`. NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`. @@ -964,20 +966,7 @@ NOTE: Defined in `active_support/core_ext/module/delegation.rb` 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. Rails uses it in a few places, for instance when it generates an association's API: - -```ruby -redefine_method("#{reflection.name}=") do |new_value| - association = association_instance_get(reflection.name) - - if association.nil? || association.target != new_value - association = association_proxy_class.new(self, reflection) - end - - association.replace(new_value) - association_instance_set(reflection.name, new_value.nil? ? nil : association) -end -``` +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` @@ -1024,7 +1013,7 @@ self.default_params = { }.freeze ``` -They can be also accessed and overridden at the instance level. +They can also be accessed and overridden at the instance level. ```ruby A.x = 1 @@ -1119,7 +1108,7 @@ end A model may find it useful to set `:instance_accessor` to `false` as a way to prevent mass-assignment from setting the attribute. -NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. `active_support/core_ext/class/attribute_accessors.rb` is deprecated and will be removed in Ruby on Rails 4.2. +NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. ### Subclasses & Descendants @@ -1178,9 +1167,9 @@ Inserting data into HTML templates needs extra care. For example, you can't just #### Safe Strings -Active Support has the concept of <i>(html) safe</i> strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. +Active Support has the concept of _(html) safe_ strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. -Strings are considered to be <i>unsafe</i> by default: +Strings are considered to be _unsafe_ by default: ```ruby "".html_safe? # => false @@ -1281,7 +1270,7 @@ The method `squish` strips leading and trailing whitespace, and substitutes runs There's also the destructive version `String#squish!`. -Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). +Note that it handles both ASCII and Unicode whitespace. NOTE: Defined in `active_support/core_ext/string/filters.rb`. @@ -1323,6 +1312,38 @@ In above examples "dear" gets cut first, but then `:separator` prevents it. NOTE: Defined in `active_support/core_ext/string/filters.rb`. +### `truncate_words` + +The method `truncate_words` returns a copy of its receiver truncated after a given number of words: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(4) +# => "Oh dear! Oh dear!..." +``` + +Ellipsis can be customized with the `:omission` option: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: '…') +# => "Oh dear! Oh dear!…" +``` + +Pass a `:separator` to truncate the string at a natural break: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!') +# => "Oh dear! Oh dear! I shall be late..." +``` + +The option `:separator` can be a regexp: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/) +# => "Oh dear! Oh dear!..." +``` + +NOTE: Defined in `active_support/core_ext/string/filters.rb`. + ### `inquiry` The `inquiry` method converts a string into a `StringInquirer` object making equality checks prettier. @@ -1403,6 +1424,8 @@ The third argument, `indent_empty_lines`, is a flag that says whether empty line The `indent!` method performs indentation in-place. +NOTE: Defined in `active_support/core_ext/string/indent.rb`. + ### Access #### `at(position)` @@ -1426,7 +1449,7 @@ Returns the substring of the string starting at position `position`: "hello".from(0) # => "hello" "hello".from(2) # => "llo" "hello".from(-2) # => "lo" -"hello".from(10) # => "" if < 1.9, nil in 1.9 +"hello".from(10) # => nil ``` NOTE: Defined in `active_support/core_ext/string/access.rb`. @@ -1642,6 +1665,9 @@ Given a string with a qualified constant name, `demodulize` returns the very con "Product".demodulize # => "Product" "Backoffice::UsersController".demodulize # => "UsersController" "Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils" +"::Inflections".demodulize # => "Inflections" +"".demodulize # => "" + ``` Active Record for example uses this method to compute the name of a counter cache column: @@ -1776,34 +1802,47 @@ NOTE: Defined in `active_support/core_ext/string/inflections.rb`. #### `humanize` -The method `humanize` gives you a sensible name for display out of an attribute name. To do so it replaces underscores with spaces, removes any "_id" suffix, and capitalizes the first word: +The method `humanize` tweaks an attribute name for display to end users. + +Specifically performs these transformations: + + * Applies human inflection rules to the argument. + * Deletes leading underscores, if any. + * Removes a "_id" suffix if present. + * Replaces underscores with spaces, if any. + * Downcases all words except acronyms. + * Capitalizes the first word. + +The capitalization of the first word can be turned off by setting the ++:capitalize+ option to false (default is true). ```ruby -"name".humanize # => "Name" -"author_id".humanize # => "Author" -"comments_count".humanize # => "Comments count" +"name".humanize # => "Name" +"author_id".humanize # => "Author" +"author_id".humanize(capitalize: false) # => "author" +"comments_count".humanize # => "Comments count" +"_id".humanize # => "Id" ``` -The capitalization of the first word can be turned off by setting the optional parameter `capitalize` to false: +If "SSL" was defined to be an acronym: ```ruby -"author_id".humanize(capitalize: false) # => "author" +'ssl_error'.humanize # => "SSL error" ``` -The helper method `full_messages` uses `humanize` as a fallback to include attribute names: +The helper method `full_messages` uses `humanize` as a fallback to include +attribute names: ```ruby def full_messages - full_messages = [] - - each do |attribute, messages| - ... - attr_name = attribute.to_s.gsub('.', '_').humanize - attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - ... - end + map { |attribute, message| full_message(attribute, message) } +end - full_messages +def full_message + ... + attr_name = attribute.to_s.tr('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, default: attr_name) + ... end ``` @@ -1913,24 +1952,6 @@ as well as adding or subtracting their results from a Time object. For example: (4.months + 5.years).from_now ``` -While these methods provide precise calculation when used as in the examples above, care -should be taken to note that this is not true if the result of `months', `years', etc is -converted before use: - -```ruby -# equivalent to 30.days.to_i.from_now -1.month.to_i.from_now - -# equivalent to 365.25.days.to_f.from_now -1.year.to_f.from_now -``` - -In such cases, Ruby's core [Date](http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html) and -[Time](http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html) should be used for precision -date and time arithmetic. - -NOTE: Defined in `active_support/core_ext/numeric/time.rb`. - ### Formatting Enables the formatting of numbers in a variety of ways. @@ -2717,11 +2738,14 @@ The method `transform_keys` accepts a block and returns a hash that has applied # => {"" => nil, "A" => :a, "1" => 1} ``` -The result in case of collision is undefined: +In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: ```ruby {"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase } -# => {"A" => 2}, in my test, can't rely on this result though +# The result could either be +# => {"A"=>2} +# or +# => {"A"=>1} ``` This method may be useful for example to build specialized conversions. For instance `stringify_keys` and `symbolize_keys` use `transform_keys` to perform their key conversions: @@ -2756,11 +2780,14 @@ The method `stringify_keys` returns a hash that has a stringified version of the # => {"" => nil, "a" => :a, "1" => 1} ``` -The result in case of collision is undefined: +In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: ```ruby {"a" => 1, a: 2}.stringify_keys -# => {"a" => 2}, in my test, can't rely on this result though +# The result could either be +# => {"a"=>2} +# or +# => {"a"=>1} ``` This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionView::Helpers::FormHelper` defines: @@ -2797,11 +2824,14 @@ The method `symbolize_keys` returns a hash that has a symbolized version of the WARNING. Note in the previous example only one key was symbolized. -The result in case of collision is undefined: +In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: ```ruby {"a" => 1, a: 2}.symbolize_keys -# => {:a=>2}, in my test, can't rely on this result though +# The result could either be +# => {:a=>2} +# or +# => {:a=>1} ``` This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionController::UrlRewriter` defines @@ -2846,6 +2876,20 @@ Active Record does not accept unknown options when building associations, for ex NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +### Working with Values + +#### `transform_values` && `transform_values!` + +The method `transform_values` accepts a block and returns a hash that has applied the block operations to each of the values in the receiver. + +```ruby +{ nil => nil, 1 => 1, :x => :a }.transform_values { |value| value.to_s.upcase } +# => {nil=>"", 1=>"1", :x=>"A"} +``` +There's also the bang variant `transform_values!` that applies the block operations to values in the very receiver. + +NOTE: Defined in `active_support/core_text/hash/transform_values.rb`. + ### Slicing Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes: @@ -2909,7 +2953,7 @@ NOTE: Defined in `active_support/core_ext/hash/indifferent_access.rb`. ### Compacting -The methods `compact` and `compact!` return a Hash without items with `nil` value. +The methods `compact` and `compact!` return a Hash without items with `nil` value. ```ruby {a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2} @@ -3656,9 +3700,9 @@ t.advance(seconds: 1) #### `Time.current` -Active Support defines `Time.current` to be today in the current time zone. That's like `Time.now`, except that it honors the user time zone, if defined. It also defines `Time.yesterday` and `Time.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Time.current`. +Active Support defines `Time.current` to be today in the current time zone. That's like `Time.now`, except that it honors the user time zone, if defined. It also defines the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Time.current`. -When making Time comparisons using methods which honor the user time zone, make sure to use `Time.current` and not `Time.now`. There are cases where the user time zone might be in the future compared to the system time zone, which `Time.today` uses by default. This means `Time.now` may equal `Time.yesterday`. +When making Time comparisons using methods which honor the user time zone, make sure to use `Time.current` instead of `Time.now`. There are cases where the user time zone might be in the future compared to the system time zone, which `Time.now` uses by default. This means `Time.now.to_date` may equal `Date.yesterday`. #### `all_day`, `all_week`, `all_month`, `all_quarter` and `all_year` @@ -3822,41 +3866,41 @@ The name may be given as a symbol or string. A symbol is tested against the bare 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 `PostsController` is called Rails tries optimistically to use `PostsHelper`. 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 `posts_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: +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: ```ruby def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path -rescue MissingSourceFile => e +rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- -Active Support adds `is_missing?` to `LoadError`, and also assigns that class to the constant `MissingSourceFile` for backwards compatibility. +Active Support adds `is_missing?` to `LoadError`. Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). -For example, when an action of `PostsController` is called Rails tries to load `posts_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases: +For example, when an action of `ArticlesController` is called Rails tries to load `articles_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases: ```ruby def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path -rescue MissingSourceFile => e +rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/load_error.rb`. diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 6c77a40d42..9d9f40e956 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Support Instrumentation ============================== @@ -17,7 +19,7 @@ After reading this guide, you will know: Introduction to instrumentation ------------------------------- -The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code. +The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in (TODO: link to section detailing each hook point). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code. For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken. @@ -135,7 +137,9 @@ Action Controller | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | +| `:status` | HTTP status code | | `:view_runtime` | Amount spent in view in ms | +| `:db_runtime` | Amount spent executing database queries in ms | ```ruby { @@ -223,11 +227,11 @@ Active Record ### sql.active_record -| Key | Value | -| ------------ | --------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:object_id` | `self.object_id` | +| Key | Value | +| ---------------- | --------------------- | +| `:sql` | SQL statement | +| `:name` | Name of the operation | +| `:connection_id` | `self.object_id` | INFO. The adapters will add their own data as well. @@ -364,7 +368,7 @@ INFO. Options passed to fetch will be merged with the payload. | ------ | --------------------- | | `:key` | Key used in the store | -INFO. Cache stores my add their own keys +INFO. Cache stores may add their own keys ```ruby { @@ -457,6 +461,7 @@ Most times you only care about the data itself. Here is a shortcut to just get t ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args| data = args.extract_options! data # { extra: :information } +end ``` You may also subscribe to events matching a regular expression. This enables you to subscribe to diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 311cc23cf0..b385bdbe83 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + API Documentation Guidelines ============================ @@ -13,7 +15,19 @@ After reading this guide, you will know: RDoc ---- -The Rails API documentation is generated with RDoc. Please consult the documentation for help with the [markup](http://rdoc.rubyforge.org/RDoc/Markup.html), and also take into account these [additional directives](http://rdoc.rubyforge.org/RDoc/Parser/Ruby.html). +The [Rails API documentation](http://api.rubyonrails.org) is generated with +[RDoc](http://docs.seattlerb.org/rdoc/). + +```bash + bundle exec rake rdoc +``` + +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), +and also take into account these [additional +directives](http://docs.seattlerb.org/rdoc/RDoc/Parser/Ruby.html). Wording ------- @@ -67,7 +81,7 @@ used. Instead of: English ------- -Please use American English (<em>color</em>, <em>center</em>, <em>modularize</em>, 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](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences). Example Code ------------ @@ -110,14 +124,14 @@ The results of expressions follow them and are introduced by "# => ", vertically If a line is too long, the comment may be placed on the next line: ```ruby -# label(:post, :title) -# # => <label for="post_title">Title</label> +# label(:article, :title) +# # => <label for="article_title">Title</label> # -# label(:post, :title, "A short title") -# # => <label for="post_title">A short title</label> +# label(:article, :title, "A short title") +# # => <label for="article_title">A short title</label> # -# label(:post, :title, "A short title", class: "title_label") -# # => <label for="post_title" class="title_label">A short title</label> +# label(:article, :title, "A short title", class: "title_label") +# # => <label for="article_title" class="title_label">A short title</label> ``` Avoid using any printing methods like `puts` or `p` for that purpose. @@ -128,8 +142,55 @@ On the other hand, regular comments do not use an arrow: # polymorphic_url(record) # same as comment_url(record) ``` -Filenames ---------- +Booleans +-------- + +In predicates and flags prefer documenting boolean semantics over exact values. + +When "true" or "false" are used as defined in Ruby use regular font. The +singletons `true` and `false` need fixed-width font. Please avoid terms like +"truthy", Ruby defines what is true and false in the language, and thus those +words have a technical meaning and need no substitutes. + +As a rule of thumb, do not document singletons unless absolutely necessary. That +prevents artificial constructs like `!!` or ternaries, allows refactors, and the +code does not need to rely on the exact values returned by methods being called +in the implementation. + +For example: + +```markdown +`config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default +``` + +the user does not need to know which is the actual default value of the flag, +and so we only document its boolean semantics. + +An example with a predicate: + +```ruby +# Returns true if the collection is empty. +# +# If the collection has been loaded +# it is equivalent to <tt>collection.size.zero?</tt>. If the +# collection has not been loaded, it is equivalent to +# <tt>collection.exists?</tt>. If the collection has not already been +# loaded and you are going to fetch the records anyway it is better to +# check <tt>collection.length.zero?</tt>. +def empty? + if loaded? + size.zero? + else + @target.blank? && !scope.exists? + end +end +``` + +The API is careful not to commit to any particular value, the method has +predicate semantics, that's enough. + +File Names +---------- As a rule of thumb, use filenames relative to the application root: @@ -163,7 +224,17 @@ class Array end ``` -WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `<tt>...</tt>`, notably symbols, setters, inline snippets, etc. +WARNING: Using `+...+` for fixed-width font only works with simple content like +ordinary method names, symbols, paths (with forward slashes), etc. Please use +`<tt>...</tt>` for everything else, notably class or module names with a +namespace as in `<tt>ActiveRecord::Base</tt>`. + +You can quickly test the RDoc output with the following command: + +``` +$ echo "+:to_param+" | rdoc --pipe +#=> <p><code>:to_param</code></p> +``` ### Regular Font @@ -229,3 +300,64 @@ self.class_eval %{ end } ``` + +Method Visibility +----------------- + +When writing documentation for Rails, it's important to understand the difference between public user-facing API vs internal API. + +Rails, like most libraries, uses the private keyword from Ruby for defining internal API. However, public API follows a slightly different convention. Instead of assuming all public methods are designed for user consumption, Rails uses the `:nodoc:` directive to annotate these kinds of methods as internal API. + +This means that there are methods in Rails with `public` visibility that aren't meant for user consumption. + +An example of this is `ActiveRecord::Core::ClassMethods#arel_table`: + +```ruby +module ActiveRecord::Core::ClassMethods + def arel_table #:nodoc: + # do some magic.. + end +end +``` + +If you thought, "this method looks like a public class method for `ActiveRecord::Core`", you were right. But actually the Rails team doesn't want users to rely on this method. So they mark it as `:nodoc:` and it's removed from public documentation. The reasoning behind this is to allow the team to change these methods according to their internal needs across releases as they see fit. The name of this method could change, or the return value, or this entire class may disappear; there's no guarantee and so you shouldn't depend on this API in your plugins or applications. Otherwise, you risk your app or gem breaking when you upgrade to a newer release of Rails. + +As a contributor, it's important to think about whether this API is meant for end-user consumption. The Rails team is committed to not making any breaking changes to public API across releases without going through a full deprecation cycle. It's recommended that you `:nodoc:` any of your internal methods/classes unless they're already private (meaning visibility), in which case it's internal by default. Once the API stabilizes the visibility can change, but changing public API is much harder due to backwards compatibility. + +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 +------------------------- + +When documenting parts of Rails API, it's important to remember all of the +pieces that go into the Rails stack. + +This means that behavior may change depending on the scope or context of the +method or class you're trying to document. + +In various places there is different behavior when you take the entire stack +into account, one such example is +`ActionView::Helpers::AssetTagHelper#image_tag`: + +```ruby +# image_tag("icon.png") +# # => <img alt="Icon" src="/assets/icon.png" /> +``` + +Although the default behavior for `#image_tag` is to always return +`/images/icon.png`, we take into account the full Rails stack (including the +Asset Pipeline) we may see the result seen above. + +We're only concerned with the behavior experienced when using the full default +Rails stack. + +In this case, we want to document the behavior of the _framework_, and not just +this specific method. + +If you have a question on how the Rails team handles certain API, don't hesitate to open a ticket or send a patch to the [issue tracker](https://github.com/rails/rails/issues). diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index bce5d6c55f..52ea605a72 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Asset Pipeline ================== @@ -56,11 +58,11 @@ the comment operator on that line to later enable the asset pipeline: To set asset compression methods, set the appropriate configuration options in `production.rb` - `config.assets.css_compressor` for your CSS and -`config.assets.js_compressor` for your Javascript: +`config.assets.js_compressor` for your JavaScript: ```ruby config.assets.css_compressor = :yui -config.assets.js_compressor = :uglify +config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included @@ -124,19 +126,22 @@ with a built-in helper. In the source the generated code looked like this: The query string strategy has several disadvantages: 1. **Not all caches will reliably cache content where the filename only differs by -query parameters**<br> +query parameters** + [Steve Souders recommends](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/), "...avoiding a querystring for cacheable resources". He found that in this case 5-20% of requests will not be cached. Query strings in particular do not work at all with some CDNs for cache invalidation. -2. **The file name can change between nodes in multi-server environments.**<br> +2. **The file name can change between nodes in multi-server environments.** + The default query string in Rails 2.x is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request. -3. **Too much cache invalidation**<br> +3. **Too much cache invalidation** + When static assets are deployed with each new release of code, the mtime (time of last modification) of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. @@ -144,7 +149,7 @@ clients to fetch them again, even when the content of those assets has not chang Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content. -Fingerprinting is enabled by default for production and disabled for all other +Fingerprinting is enabled by default for both the development and production environments. You can enable or disable it in your configuration through the `config.assets.digest` option. @@ -163,9 +168,9 @@ pipeline, the preferred location for these assets is now the `app/assets` directory. Files in this directory are served by the Sprockets middleware. Assets can still be placed in the `public` hierarchy. Any assets under `public` -will be served as static files by the application or web server. You should use -`app/assets` for files that must undergo some pre-processing before they are -served. +will be served as static files by the application or web server when +`config.serve_static_files` is set to true. You should use `app/assets` for +files that must undergo some pre-processing before they are served. In production, Rails precompiles these files to `public/assets` by default. The precompiled copies are then served as static assets by the web server. The files @@ -177,12 +182,12 @@ 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.css.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 -file at `app/assets/javascripts/projects.js.coffee` and another at -`app/assets/stylesheets/projects.css.scss`. By default these files will be ready +file at `app/assets/javascripts/projects.coffee` and another at +`app/assets/stylesheets/projects.scss`. By default these files will be ready to use by your application immediately using the `require_tree` directive. See [Manifest Files and Directives](#manifest-files-and-directives) for more details on require_tree. @@ -198,18 +203,13 @@ 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. This will -result in false positives during development as these files will work just fine -since assets are compiled on the fly in development mode. When running in -production, however, you will see 500 errors since live compilation is turned -off by default. See [Precompiling Assets](#precompiling-assets) for more -information on how precompiling works. +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 -your operating system. Check -[ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all -supported JavaScript runtimes. +your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes. You can also disable generation of controller specific asset files by adding the following to your `config/application.rb` configuration: @@ -232,7 +232,9 @@ images, JavaScript files or stylesheets. scope of the application or those libraries which are shared across applications. * `vendor/assets` is for assets that are owned by outside entities, such as -code for JavaScript plugins and CSS frameworks. +code for JavaScript plugins and CSS frameworks. Keep in mind that third party +code with references to other files also processed by the asset Pipeline (images, +stylesheets, etc.), will need to be rewritten to use helpers like `asset_path`. WARNING: If you are upgrading from Rails 3, please take into account that assets under `lib/assets` or `vendor/assets` are available for inclusion via the @@ -245,7 +247,7 @@ When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it. The default locations are: the `images`, `javascripts` and `stylesheets` -directories under the `apps/assets` folder, but these subdirectories +directories under the `app/assets` folder, but these subdirectories are not special - any path under `assets/*` will be searched. For example, these files: @@ -302,7 +304,7 @@ Sprockets uses files named `index` (with the relevant extensions) for a special purpose. For example, if you have a jQuery library with many modules, which is stored in -`lib/assets/library_name`, the file `lib/assets/library_name/index.js` serves as +`lib/assets/javascripts/library_name`, the file `lib/assets/javascripts/library_name/index.js` serves as the manifest for all files in this library. This file could include a list of all the required files in order, or a simple `require_tree` directive. @@ -422,7 +424,7 @@ $('#logo').attr({ src: "<%= asset_path('logo.png') %>" }); This writes the path to the particular asset being referenced. Similarly, you can use the `asset_path` helper in CoffeeScript files with `erb` -extension (e.g., `application.js.coffee.erb`): +extension (e.g., `application.coffee.erb`): ```js $('#logo').attr src: "<%= asset_path('logo.png') %>" @@ -493,19 +495,13 @@ The directives that work in JavaScript files also work in stylesheets one, requiring all stylesheets from the current directory. In this example, `require_self` is used. This puts the CSS contained within the -file (if any) at the precise location of the `require_self` call. If -`require_self` is called more than once, only the last call is respected. - -NOTE. If you want to use multiple Sass files, you should generally use the [Sass -`@import` -rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) instead -of these Sprockets directives. Using Sprockets directives all Sass files exist -within their own scope, making variables or mixins only available within the -document they were defined in. You can do file globbing as well using -`@import "*"`, and `@import "**/*"` to add the whole tree equivalent to how -`require_tree` works. Check the [sass-rails -documentation](https://github.com/rails/sass-rails#features) for more info and -important caveats. +file (if any) at the precise location of the `require_self` call. + +NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) +instead of these Sprockets directives. When using Sprockets directives, Sass files exist within +their own scope, making variables or mixins only available within the document they were defined in. + +You can do file globbing as well using `@import "*"`, and `@import "**/*"` to add the whole tree which is equivalent to how `require_tree` works. Check the [sass-rails documentation](https://github.com/rails/sass-rails#features) for more info and important caveats. You can have as many manifest files as you need. For example, the `admin.css` and `admin.js` manifest could contain the JS and CSS files that are used for the @@ -529,8 +525,8 @@ The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which -generated an `app/assets/javascripts/projects.js.coffee` and an -`app/assets/stylesheets/projects.css.scss` file. +generated an `app/assets/javascripts/projects.coffee` and an +`app/assets/stylesheets/projects.scss` file. In development mode, or if the asset pipeline is disabled, when these files are requested they are processed by the processors provided by the `coffee-script` @@ -542,13 +538,13 @@ web server. Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet -called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB, +called `app/assets/stylesheets/projects.scss.erb` is first processed as ERB, then SCSS, and finally served as CSS. The same applies to a JavaScript file - -`app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then +`app/assets/javascripts/projects.coffee.erb` is processed as ERB, then CoffeeScript, and served as JavaScript. Keep in mind the order of these preprocessors is important. For example, if -you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee` +you called your JavaScript file `app/assets/javascripts/projects.erb.coffee` then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems. @@ -586,23 +582,21 @@ runtime. To disable this behavior you can set: config.assets.raise_runtime_errors = false ``` -When `raise_runtime_errors` is set to `false` sprockets will not check that dependencies of assets are declared properly. Here is a scenario where you must tell the asset pipeline about a dependency: - -If you have `application.css.erb` that references `logo.png` like this: +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. -```css -#logo { background: url(<%= asset_data_uri 'logo.png' %>) } -``` +### Turning Digests Off -Then you must declare that `logo.png` is a dependency of `application.css.erb`, so when the image gets re-compiled, the css file does as well. You can do this using the `//= depend_on_asset` declaration: +You can turn off digests by updating `config/environments/development.rb` to +include: -```css -//= depend_on_asset "logo.png" -#logo { background: url(<%= asset_data_uri 'logo.png' %>) } +```ruby +config.assets.digest = false ``` -Without this declaration you may experience strange behavior when pushing to production that is difficult to debug. When you have `raise_runtime_errors` set to `true`, dependencies will be checked at runtime so you can ensure that all dependencies are met. - +When this option is true, digests will be generated for asset URLs. ### Turning Debugging Off @@ -696,7 +690,7 @@ information on compiling locally. The rake task is: ```bash -$ RAILS_ENV=production bundle exec rake assets:precompile +$ RAILS_ENV=production bin/rake assets:precompile ``` Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. @@ -719,7 +713,7 @@ The default matcher for compiling files includes `application.js`, automatically) from `app/assets` folders including your gems: ```ruby -[ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) }, +[ Proc.new { |filename, path| path =~ /app\/assets/ && !%w(.js .css).include?(File.extname(filename)) }, /application.(css|js)$/ ] ``` @@ -729,25 +723,25 @@ JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS. If you have other manifests or individual stylesheets and JavaScript files to -include, you can add them to the `precompile` array in `config/application.rb`: +include, you can add them to the `precompile` array in `config/initializers/assets.rb`: ```ruby -config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] +Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] ``` Or, you can opt to precompile all assets with something like this: ```ruby -# config/application.rb -config.assets.precompile << Proc.new do |path| +# config/initializers/assets.rb +Rails.application.config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path if full_path.starts_with? app_assets_path - puts "including asset: " + full_path + logger.info "including asset: " + full_path true else - puts "excluding asset: " + full_path + logger.info "excluding asset: " + full_path false end else @@ -770,7 +764,7 @@ typical manifest file looks like: "digest":"12b3c7dd74d2e9df37e7cbb1efa76a6d"},"application-1c5752789588ac18d7e1a50b1f0fd4c2.css":{"logical_path":"application.css","mtime":"2013-07-26T22:56:17-07:00","size":1591, "digest":"1c5752789588ac18d7e1a50b1f0fd4c2"},"favicon-a9c641bf2b81f0476e876f7c5e375969.ico":{"logical_path":"favicon.ico","mtime":"2013-07-26T23:00:10-07:00","size":1406, "digest":"a9c641bf2b81f0476e876f7c5e375969"},"my_image-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"my_image.png","mtime":"2013-07-26T23:00:27-07:00","size":6646, -"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets"{"application.js": +"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets":{"application.js": "application-723d1be6cc741a3aabb1cec24276d681.js","application.css": "application-1c5752789588ac18d7e1a50b1f0fd4c2.css", "favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png": @@ -786,7 +780,7 @@ exception indicating the name of the missing file(s). #### Far-future Expires Header -Precompiled assets exist on the filesystem and are served directly by your web +Precompiled assets exist on the file system and are served directly by your web server. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add those headers. @@ -798,13 +792,15 @@ For Apache: # `mod_expires` to be enabled. <Location /assets/> # Use of ETag is discouraged when Last-Modified is present - Header unset ETag FileETag None + Header unset ETag + FileETag None # RFC says only cache for 1 year - ExpiresActive On ExpiresDefault "access plus 1 year" + ExpiresActive On + ExpiresDefault "access plus 1 year" </Location> ``` -For nginx: +For NGINX: ```nginx location ~ ^/assets/ { @@ -826,7 +822,7 @@ compression ratio, thus reducing the size of the data transfer to the minimum. On the other hand, web servers can be configured to serve compressed content directly from disk, rather than deflating non-compressed files themselves. -Nginx is able to do this automatically enabling `gzip_static`: +NGINX is able to do this automatically enabling `gzip_static`: ```nginx location ~ ^/(assets)/ { @@ -845,7 +841,7 @@ the module compiled. Otherwise, you may need to perform a manual compilation: ./configure --with-http_gzip_static_module ``` -If you're compiling nginx with Phusion Passenger you'll need to pass that option +If you're compiling NGINX with Phusion Passenger you'll need to pass that option when prompted. A robust configuration for Apache is possible but tricky; please Google around. @@ -864,10 +860,12 @@ duplication of work. Local compilation allows you to commit the compiled files into source control, and deploy as normal. -There are two caveats: +There are three caveats: * You must not run the Capistrano deployment task that precompiles assets. -* You must change the following two application configuration settings. +* You must ensure any necessary compressors or minifiers are +available on your development system. +* You must change the following application configuration setting: In `config/environments/development.rb`, place the following line: @@ -881,9 +879,6 @@ development mode, and pass all requests to Sprockets. The prefix is still set to would serve the precompiled assets from `/assets` in development, and you would not see any local changes until you compile assets again. -You will also need to ensure any necessary compressors or minifiers are -available on your development system. - In practice, this will allow you to precompile locally, have those files in your working tree, and commit those files to source control when needed. Development mode will work as expected. @@ -923,22 +918,213 @@ end ### CDNs -If your assets are being served by a CDN, ensure they don't stick around in your -cache forever. This can cause problems. If you use -`config.action_controller.perform_caching = true`, Rack::Cache will use -`Rails.cache` to store assets. This can cause your cache to fill up quickly. +CDN stands for [Content Delivery +Network](http://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 +best practice is to use a CDN in front of your application. + +A common pattern for using a CDN is to set your production application as the +"origin" server. This means when a browser requests an asset from the CDN and +there is a cache miss, it will grab the file from your server on the fly and +then cache it. For example if you are running a Rails application on +`example.com` and have a CDN configured at `mycdnsubdomain.fictional-cdn.com`, +then when a request is made to `mycdnsubdomain.fictional- +cdn.com/assets/smile.png`, the CDN will query your server once at +`example.com/assets/smile.png` and cache the request. The next request to the +CDN that comes in to the same URL will hit the cached copy. When the CDN can +serve an asset directly the request never touches your Rails server. Since the +assets from a CDN are geographically closer to the browser, the request is +faster, and since your server doesn't need to spend time serving assets, it can +focus on serving application code as fast as possible. + +#### Set up a CDN to Serve Static Assets + +To set up your CDN you have to have your application running in production on +the internet at a publically available URL, for example `example.com`. Next +you'll need to sign up for a CDN service from a cloud hosting provider. When you +do this you need to configure the "origin" of the CDN to point back at your +website `example.com`, check your provider for documentation on configuring the +origin server. + +The CDN you provisioned should give you a custom subdomain for your application +such as `mycdnsubdomain.fictional-cdn.com` (note fictional-cdn.com is not a +valid CDN provider at the time of this writing). Now that you have configured +your CDN server, you need to tell browsers to use your CDN to grab assets +instead of your Rails server directly. You can do this by configuring Rails to +set your CDN as the asset host instead of using a relative path. To set your +asset host in Rails, you need to set `config.action_controller.asset_host` in +`config/production.rb`: -Every cache is different, so evaluate how your CDN handles caching and make sure -that it plays nicely with the pipeline. You may find quirks related to your -specific set up, you may not. The defaults nginx uses, for example, should give -you no problems when used as an HTTP cache. +```ruby +config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com' +``` + +NOTE: You only need to provide the "host", this is the subdomain and root +domain, you do not need to specify a protocol or "scheme" such as `http://` or +`https://`. When a web page is requested, the protocol in the link to your asset +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 +staging copy of your site easier: + +``` +config.action_controller.asset_host = ENV['CDN_HOST'] +``` + + + +Note: You would need to set `CDN_HOST` on your server to `mycdnsubdomain +.fictional-cdn.com` for this to work. + +Once you have configured your server and your CDN when you serve a webpage that +has an asset: + +```erb +<%= asset_path('smile.png') %> +``` + +Instead of returning a path such as `/assets/smile.png` (digests are left out +for readability). The URL generated will have the full path to your CDN. + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile.png +``` + +If the CDN has a copy of `smile.png` it will serve it to the browser and your +server doesn't even know it was requested. If the CDN does not have a copy it +will try to find it a the "origin" `example.com/assets/smile.png` and then store +it for future use. + +If you want to serve only some assets from your CDN, you can use custom `:host` +option your asset helper, which overwrites value set in +`config.action_controller.asset_host`. + +```erb +<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %> +``` + +#### Customize CDN Caching Behavior + +A CDN works by caching content. If the CDN has stale or bad content, then it is +hurting rather than helping your application. The purpose of this section is to +describe general caching behavior of most CDNs, your specific provider may +behave slightly differently. + +##### CDN Request Caching + +While a CDN is described as being good for caching assets, in reality caches the +entire request. This includes the body of the asset as well as any headers. The +most important one being `Cache-Control` which tells the CDN (and web browsers) +how to cache contents. This means that if someone requests an asset that does +not exist `/assets/i-dont-exist.png` and your Rails application returns a 404, +then your CDN will likely cache the 404 page if a valid `Cache-Control` header +is present. + +##### CDN Header Debugging + +One way to check the headers are cached properly in your CDN is by using [curl]( +http://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com). You +can request the headers from both your server and your CDN to verify they are +the same: + +``` +$ curl -I http://www.example/assets/application- +d0e099e021c95eb0de3615fd1d8c4d83.css +HTTP/1.1 200 OK +Server: Cowboy +Date: Sun, 24 Aug 2014 20:27:50 GMT +Connection: keep-alive +Last-Modified: Thu, 08 May 2014 01:24:14 GMT +Content-Type: text/css +Cache-Control: public, max-age=2592000 +Content-Length: 126560 +Via: 1.1 vegur +``` + +Versus the CDN copy. + +``` +$ curl -I http://mycdnsubdomain.fictional-cdn.com/application- +d0e099e021c95eb0de3615fd1d8c4d83.css +HTTP/1.1 200 OK Server: Cowboy Last- +Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css +Cache-Control: +public, max-age=2592000 +Via: 1.1 vegur +Content-Length: 126560 +Accept-Ranges: +bytes +Date: Sun, 24 Aug 2014 20:28:45 GMT +Via: 1.1 varnish +Age: 885814 +Connection: keep-alive +X-Served-By: cache-dfw1828-DFW +X-Cache: HIT +X-Cache-Hits: +68 +X-Timer: S1408912125.211638212,VS0,VE0 +``` + +Check your CDN documentation for any additional information they may provide +such as `X-Cache` or for any additional headers they may add. + +##### CDNs and the Cache-Control Header + +The [cache control +header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C +specification that describes how a request can be cached. When no CDN is used, a +browser will use this information to cache contents. This is very helpful for +assets that are not modified so that a browser does not need to re-download a +website's CSS or javascript on every request. Generally we want our Rails server +to tell our CDN (and browser) that the asset is "public", that means any cache +can store the request. Also we commonly want to set `max-age` which is how long +the cache will store the object before invalidating the cache. The `max-age` +value is set to seconds with a maximum possible value of `31536000` which is one +year. You can do this in your rails application by setting + +``` +config.static_cache_control = "public, max-age=31536000" +``` + +Now when your application serves an asset in production, the CDN will store the +asset for up to a year. Since most CDNs also cache headers of the request, this +`Cache-Control` will be passed along to all future browsers seeking this asset, +the browser then knows that it can store this asset for a very long time before +needing to re-request it. + +##### CDNs and URL based Cache Invalidation + +Most CDNs will cache contents of an asset based on the complete URL. This means +that a request to + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png +``` + +Will be a completely different cache from + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile.png +``` + +If you want to set far future `max-age` in your `Cache-Control` (and you do), +then make sure when you change your assets that your cache is invalidated. For +example when changing the smiley face in an image from yellow to blue, you want +all visitors of your site to get the new blue face. When using a CDN with the +Rails asset pipeline `config.assets.digest` is set to true by default so that +each asset will have a different file name when it is changed. This way you +don't have to ever manually invalidate any items in your cache. By using a +different unique asset name instead, your users get the latest asset. Customizing the Pipeline ------------------------ ### CSS Compression -There is currently one option for compressing CSS, YUI. The [YUI CSS +One of the options for compressing CSS is YUI. The [YUI CSS compressor](http://yui.github.io/yuicompressor/css.html) provides minification. @@ -948,6 +1134,11 @@ gem. ```ruby config.assets.css_compressor = :yui ``` +The other option for compressing CSS if you have the sass-rails gem installed is + +```ruby +config.assets.css_compressor = :sass +``` ### JavaScript Compression @@ -1018,14 +1209,15 @@ The X-Sendfile header is a directive to the web server to ignore the response from the application, and instead serve a specified file from disk. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is -faster. +faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file) +on how to use this feature. -Apache and nginx support this option, which can be enabled in +Apache and NGINX support this option, which can be enabled in `config/environments/production.rb`: ```ruby -# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache -# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx +# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache +# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX ``` WARNING: If you are upgrading an existing application and intend to use this @@ -1033,6 +1225,10 @@ option, take care to paste this configuration option only into `production.rb` and any other environments you define with production behavior (not `application.rb`). +TIP: For further details have a look at the docs of your production web server: +- [Apache](https://tn123.org/mod_xsendfile/) +- [NGINX](http://wiki.nginx.org/XSendfile) + Assets Cache Store ------------------ @@ -1051,6 +1247,14 @@ cache store. config.assets.cache_store = :memory_store, { size: 32.megabytes } ``` +To disable the assets cache store: + +```ruby +config.assets.configure do |env| + env.cache = ActiveSupport::Cache.lookup_store(:null_store) +end +``` + Adding Assets to Your Gems -------------------------- @@ -1145,9 +1349,9 @@ config.assets.digest = true ``` Rails 4 no longer sets default config values for Sprockets in `test.rb`, so -`test.rb` now requies Sprockets configuration. The old defaults in the test -environment are: `config.assets.compile = true`, `config.assets.compress = -false`, `config.assets.debug = false` and `config.assets.digest = false`. +`test.rb` now requires Sprockets configuration. The old defaults in the test +environment are: `config.assets.compile = true`, `config.assets.compress = false`, +`config.assets.debug = false` and `config.assets.digest = false`. The following should also be added to `Gemfile`: diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 9867d2dc3f..dab885a8fb 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Associations ========================== @@ -101,13 +103,13 @@ class CreateOrders < ActiveRecord::Migration def change create_table :customers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :orders do |t| - t.belongs_to :customer + t.belongs_to :customer, index: true t.datetime :order_date - t.timestamps + t.timestamps null: false end end end @@ -132,13 +134,13 @@ class CreateSuppliers < ActiveRecord::Migration def change create_table :suppliers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :accounts do |t| - t.belongs_to :supplier + t.belongs_to :supplier, index: true t.string :account_number - t.timestamps + t.timestamps null: false end end end @@ -165,13 +167,13 @@ class CreateCustomers < ActiveRecord::Migration def change create_table :customers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :orders do |t| - t.belongs_to :customer + t.belongs_to :customer, index:true t.datetime :order_date - t.timestamps + t.timestamps null: false end end end @@ -207,19 +209,19 @@ class CreateAppointments < ActiveRecord::Migration def change create_table :physicians do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :patients do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :appointments do |t| - t.belongs_to :physician - t.belongs_to :patient + t.belongs_to :physician, index: true + t.belongs_to :patient, index: true t.datetime :appointment_date - t.timestamps + t.timestamps null: false end end end @@ -291,19 +293,19 @@ class CreateAccountHistories < ActiveRecord::Migration def change create_table :suppliers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :accounts do |t| - t.belongs_to :supplier + t.belongs_to :supplier, index: true t.string :account_number - t.timestamps + t.timestamps null: false end create_table :account_histories do |t| - t.belongs_to :account + t.belongs_to :account, index: true t.integer :credit_rating - t.timestamps + t.timestamps null: false end end end @@ -332,17 +334,17 @@ class CreateAssembliesAndParts < ActiveRecord::Migration def change create_table :assemblies do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :parts do |t| t.string :part_number - t.timestamps + t.timestamps null: false end create_table :assemblies_parts, id: false do |t| - t.belongs_to :assembly - t.belongs_to :part + t.belongs_to :assembly, index: true + t.belongs_to :part, index: true end end end @@ -371,14 +373,16 @@ class CreateSuppliers < ActiveRecord::Migration def change create_table :suppliers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :accounts do |t| t.integer :supplier_id t.string :account_number - t.timestamps + t.timestamps null: false end + + add_index :accounts, :supplier_id end end ``` @@ -453,8 +457,10 @@ class CreatePictures < ActiveRecord::Migration t.string :name t.integer :imageable_id t.string :imageable_type - t.timestamps + t.timestamps null: false end + + add_index :pictures, :imageable_id end end ``` @@ -466,8 +472,8 @@ class CreatePictures < ActiveRecord::Migration def change create_table :pictures do |t| t.string :name - t.references :imageable, polymorphic: true - t.timestamps + t.references :imageable, polymorphic: true, index: true + t.timestamps null: false end end end @@ -490,6 +496,19 @@ end With this setup, you can retrieve `@employee.subordinates` and `@employee.manager`. +In your migrations/schema, you will add a references column to the model itself. + +```ruby +class CreateEmployees < ActiveRecord::Migration + def change + create_table :employees do |t| + t.references :manager, index: true + t.timestamps null: false + end + end +end +``` + Tips, Tricks, and Warnings -------------------------- @@ -548,6 +567,8 @@ class CreateOrders < ActiveRecord::Migration t.string :order_number t.integer :customer_id end + + add_index :orders, :customer_id end end ``` @@ -558,7 +579,7 @@ If you create an association some time after you build the underlying model, you 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 order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering. -WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper\_boxes" and "papers" to generate a join table name of "papers\_paper\_boxes" because of the length of the name "paper\_boxes", but it in fact generates a join table name of "paper\_boxes\_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings). +WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings). Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations: @@ -581,6 +602,9 @@ class CreateAssembliesPartsJoinTable < ActiveRecord::Migration t.integer :assembly_id t.integer :part_id end + + add_index :assemblies_parts, :assembly_id + add_index :assemblies_parts, :part_id end end ``` @@ -734,7 +758,7 @@ class Order < ActiveRecord::Base end ``` -Each instance of the order model will have these methods: +Each instance of the `Order` model will have these methods: ```ruby customer @@ -857,10 +881,12 @@ class Order < ActiveRecord::Base belongs_to :customer, counter_cache: :count_of_orders end class Customer < ActiveRecord::Base - has_many :orders + has_many :orders, counter_cache: :count_of_orders end ``` +NOTE: You only need to specify the :counter_cache option on the "has_many side" of the association when using a custom name for the counter cache. + Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`. ##### `:dependent` @@ -1118,7 +1144,7 @@ The `has_one` association supports these options: ##### `:as` -Setting the `:as` option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>. +Setting the `:as` option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail [earlier in this guide](#polymorphic-associations). ##### `:autosave` @@ -1190,7 +1216,7 @@ The `:source_type` option specifies the source association type for a `has_one : ##### `:through` -The `:through` option specifies a join model through which to perform the query. `has_one :through` associations were discussed in detail <a href="#the-has-one-through-association">earlier in this guide</a>. +The `:through` option specifies a join model through which to perform the query. `has_one :through` associations were discussed in detail [earlier in this guide](#the-has-one-through-association). ##### `:validate` @@ -1299,9 +1325,9 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` -* `collection=objects` +* `collection=(objects)` * `collection_singular_ids` -* `collection_singular_ids=ids` +* `collection_singular_ids=(ids)` * `collection.clear` * `collection.empty?` * `collection.size` @@ -1320,16 +1346,16 @@ class Customer < ActiveRecord::Base end ``` -Each instance of the customer model will have these methods: +Each instance of the `Customer` model will have these methods: ```ruby orders(force_reload = false) orders<<(object, ...) orders.delete(object, ...) orders.destroy(object, ...) -orders=objects +orders=(objects) order_ids -order_ids=ids +order_ids=(ids) orders.clear orders.empty? orders.size @@ -1377,7 +1403,7 @@ The `collection.destroy` method removes one or more objects from the collection WARNING: Objects will _always_ be removed from the database, ignoring the `:dependent` option. -##### `collection=objects` +##### `collection=(objects)` The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. @@ -1389,7 +1415,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects @order_ids = @customer.order_ids ``` -##### `collection_singular_ids=ids` +##### `collection_singular_ids=(ids)` The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. @@ -1473,6 +1499,7 @@ The `has_many` association supports these options: * `:as` * `:autosave` * `:class_name` +* `:counter_cache` * `:dependent` * `:foreign_key` * `:inverse_of` @@ -1484,7 +1511,7 @@ The `has_many` association supports these options: ##### `:as` -Setting the `:as` option indicates that this is a polymorphic association, as discussed <a href="#polymorphic-associations">earlier in this guide</a>. +Setting the `:as` option indicates that this is a polymorphic association, as discussed [earlier in this guide](#polymorphic-associations). ##### `:autosave` @@ -1500,6 +1527,10 @@ class Customer < ActiveRecord::Base end ``` +##### `:counter_cache` + +This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to). + ##### `:dependent` Controls what happens to the associated objects when their owner is destroyed: @@ -1510,8 +1541,6 @@ Controls what happens to the associated objects when their owner is destroyed: * `:restrict_with_exception` causes an exception to be raised if there are any associated records * `:restrict_with_error` causes an error to be added to the owner if there are any associated objects -NOTE: This option is ignored when you use the `:through` option on the association. - ##### `:foreign_key` By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: @@ -1566,7 +1595,7 @@ The `:source_type` option specifies the source association type for a `has_many ##### `:through` -The `:through` option specifies a join model through which to perform the query. `has_many :through` associations provide a way to implement many-to-many relationships, as discussed <a href="#the-has-many-through-association">earlier in this guide</a>. +The `:through` option specifies a join model through which to perform the query. `has_many :through` associations provide a way to implement many-to-many relationships, as discussed [earlier in this guide](#the-has-many-through-association). ##### `:validate` @@ -1619,7 +1648,7 @@ If you use a hash-style `where` option, then record creation via this associatio ##### `extending` -The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>. +The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail [later in this guide](#association-extensions). ##### `group` @@ -1712,58 +1741,58 @@ mostly useful together with the `:through` option. ```ruby class Person < ActiveRecord::Base has_many :readings - has_many :posts, through: :readings + has_many :articles, through: :readings end person = Person.create(name: 'John') -post = Post.create(name: 'a1') -person.posts << post -person.posts << post -person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">] -Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>] +article = Article.create(name: 'a1') +person.articles << article +person.articles << article +person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">] +Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>] ``` -In the above case there are two readings and `person.posts` brings out both of -them even though these records are pointing to the same post. +In the above case there are two readings and `person.articles` brings out both of +them even though these records are pointing to the same article. Now let's set `distinct`: ```ruby class Person has_many :readings - has_many :posts, -> { distinct }, through: :readings + has_many :articles, -> { distinct }, through: :readings end person = Person.create(name: 'Honda') -post = Post.create(name: 'a1') -person.posts << post -person.posts << post -person.posts.inspect # => [#<Post id: 7, name: "a1">] -Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>] +article = Article.create(name: 'a1') +person.articles << article +person.articles << article +person.articles.inspect # => [#<Article id: 7, name: "a1">] +Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>] ``` -In the above case there are still two readings. However `person.posts` shows -only one post because the collection loads only unique records. +In the above case there are still two readings. However `person.articles` shows +only one article because the collection loads only unique records. If you want to make sure that, upon insertion, all of the records in the persisted association are distinct (so that you can be sure that when you inspect the association that you will never find duplicate records), you should add a unique index on the table itself. For example, if you have a table named -`person_posts` and you want to make sure all the posts are unique, you could +`person_articles` and you want to make sure all the articles are unique, you could add the following in a migration: ```ruby -add_index :person_posts, :post, unique: true +add_index :person_articles, :article, unique: true ``` Note that checking for uniqueness using something like `include?` is subject to race conditions. Do not attempt to use `include?` to enforce distinctness -in an association. For instance, using the post example from above, the +in an association. For instance, using the article example from above, the following code would be racy because multiple users could be attempting this at the same time: ```ruby -person.posts << post unless person.posts.include?(post) +person.articles << article unless person.articles.include?(article) ``` #### When are Objects Saved? @@ -1788,9 +1817,9 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` -* `collection=objects` +* `collection=(objects)` * `collection_singular_ids` -* `collection_singular_ids=ids` +* `collection_singular_ids=(ids)` * `collection.clear` * `collection.empty?` * `collection.size` @@ -1809,16 +1838,16 @@ class Part < ActiveRecord::Base end ``` -Each instance of the part model will have these methods: +Each instance of the `Part` model will have these methods: ```ruby assemblies(force_reload = false) assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) -assemblies=objects +assemblies=(objects) assembly_ids -assembly_ids=ids +assembly_ids=(ids) assemblies.clear assemblies.empty? assemblies.size @@ -1873,7 +1902,7 @@ The `collection.destroy` method removes one or more objects from the collection @part.assemblies.destroy(@assembly1) ``` -##### `collection=objects` +##### `collection=(objects)` The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. @@ -1885,7 +1914,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects @assembly_ids = @part.assembly_ids ``` -##### `collection_singular_ids=ids` +##### `collection_singular_ids=(ids)` The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. @@ -2069,7 +2098,7 @@ If you use a hash-style `where`, then record creation via this association will ##### `extending` -The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>. +The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail [later in this guide](#association-extensions). ##### `group` diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md new file mode 100644 index 0000000000..f32714f893 --- /dev/null +++ b/guides/source/autoloading_and_reloading_constants.md @@ -0,0 +1,1300 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Autoloading and Reloading Constants +=================================== + +This guide documents how constant autoloading and reloading works. + +After reading this guide, you will know: + +* Key aspects of Ruby constants +* What is `autoload_paths` +* How constant autoloading works +* What is `require_dependency` +* How constant reloading works +* Solutions to common autoloading gotchas + +-------------------------------------------------------------------------------- + + +Introduction +------------ + +Ruby on Rails allows applications to be written as if their code was preloaded. + +In a normal Ruby program classes need to load their dependencies: + +```ruby +require 'application_controller' +require 'post' + +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +Our Rubyist instinct quickly sees some redundancy in there: If classes were +defined in files matching their name, couldn't their loading be automated +somehow? We could save scanning the file for dependencies, which is brittle. + +Moreover, `Kernel#require` loads files once, but development is much more smooth +if code gets refreshed when it changes without restarting the server. It would +be nice to be able to use `Kernel#load` in development, and `Kernel#require` in +production. + +Indeed, those features are provided by Ruby on Rails, where we just write + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +This guide documents how that works. + + +Constants Refresher +------------------- + +While constants are trivial in most programming languages, they are a rich +topic in Ruby. + +It is beyond the scope of this guide to document Ruby constants, but we are +nevertheless going to highlight a few key topics. Truly grasping the following +sections is instrumental to understanding constant autoloading and reloading. + +### Nesting + +Class and module definitions can be nested to create namespaces: + +```ruby +module XML + class SAXParser + # (1) + end +end +``` + +The *nesting* at any given place is the collection of enclosing nested class and +module objects outwards. For example, in the previous example, the nesting at +(1) is + +```ruby +[XML::SAXParser, XML] +``` + +It is important to understand that the nesting is composed of class and module +*objects*, it has nothing to do with the constants used to access them, and is +also unrelated to their names. + +For instance, while this definition is similar to the previous one: + +```ruby +class XML::SAXParser + # (2) +end +``` + +the nesting in (2) is different: + +```ruby +[XML::SAXParser] +``` + +`XML` does not belong to it. + +We can see in this example that the name of a class or module that belongs to a +certain nesting does not necessarily correlate with the namespaces at the spot. + +Even more, they are totally independent, take for instance + +```ruby +module X::Y + module A::B + # (3) + end +end +``` + +The nesting in (3) consists of two module objects: + +```ruby +[A::B, X::Y] +``` + +So, it not only doesn't end in `A`, which does not even belong to the nesting, +but it also contains `X::Y`, which is independent from `A::B`. + +The nesting is an internal stack maintained by the interpreter, and it gets +modified according to these rules: + +* The class object following a `class` keyword gets pushed when its body is +executed, and popped after it. + +* The module object following a `module` keyword gets pushed when its body is +executed, and popped after it. + +* A singleton class opened with `class << object` gets pushed, and popped later. + +* When any of the `*_eval` family of methods is called using a string argument, +the singleton class of the receiver is pushed to the nesting of the eval'ed +code. + +* The nesting at the top-level of code interpreted by `Kernel#load` is empty +unless the `load` call receives a true value as second argument, in which case +a newly created anonymous module is pushed by Ruby. + +It is interesting to observe that blocks do not modify the stack. In particular +the blocks that may be passed to `Class.new` and `Module.new` do not get the +class or module being defined pushed to their nesting. That's one of the +differences between defining classes and modules in one way or another. + +The nesting at any given place can be inspected with `Module.nesting`. + +### Class and Module Definitions are Constant Assignments + +Let's suppose the following snippet creates a class (rather than reopening it): + +```ruby +class C +end +``` + +Ruby creates a constant `C` in `Object` and stores in that constant a class +object. The name of the class instance is "C", a string, named after the +constant. + +That is, + +```ruby +class Project < ActiveRecord::Base +end +``` + +performs a constant assignment equivalent to + +```ruby +Project = Class.new(ActiveRecord::Base) +``` + +including setting the name of the class as a side-effect: + +```ruby +Project.name # => "Project" +``` + +Constant assignment has a special rule to make that happen: if the object +being assigned is an anonymous class or module, Ruby sets the object's name to +the name of the constant. + +INFO. From then on, what happens to the constant and the instance does not +matter. For example, the constant could be deleted, the class object could be +assigned to a different constant, be stored in no constant anymore, etc. Once +the name is set, it doesn't change. + +Similarly, module creation using the `module` keyword as in + +```ruby +module Admin +end +``` + +performs a constant assignment equivalent to + +```ruby +Admin = Module.new +``` + +including setting the name as a side-effect: + +```ruby +Admin.name # => "Admin" +``` + +WARNING. The execution context of a block passed to `Class.new` or `Module.new` +is not entirely equivalent to the one of the body of the definitions using the +`class` and `module` keywords. But both idioms result in the same constant +assignment. + +Thus, when one informally says "the `String` class", that really means: the +class object stored in the constant called "String" in the class object stored +in the `Object` constant. `String` is otherwise an ordinary Ruby constant and +everything related to constants such as resolution algorithms applies to it. + +Likewise, in the controller + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If +all is good, the constant evaluates to an object that responds to `all`. + +That is why we talk about *constant* autoloading, Rails has the ability to +load constants on the fly. + +### Constants are Stored in Modules + +Constants belong to modules in a very literal sense. Classes and modules have +a constant table; think of it as a hash table. + +Let's analyze an example to really understand what that means. While common +abuses of language like "the `String` class" are convenient, the exposition is +going to be precise here for didactic purposes. + +Let's consider the following module definition: + +```ruby +module Colors + RED = '0xff0000' +end +``` + +First, when the `module` keyword is processed, the interpreter creates a new +entry in the constant table of the class object stored in the `Object` constant. +Said entry associates the name "Colors" to a newly created module object. +Furthermore, the interpreter sets the name of the new module object to be the +string "Colors". + +Later, when the body of the module definition is interpreted, a new entry is +created in the constant table of the module object stored in the `Colors` +constant. That entry maps the name "RED" to the string "0xff0000". + +In particular, `Colors::RED` is totally unrelated to any other `RED` constant +that may live in any other class or module object. If there were any, they +would have separate entries in their respective constant tables. + +Pay special attention in the previous paragraphs to the distinction between +class and module objects, constant names, and value objects associated to them +in constant tables. + +### Resolution Algorithms + +#### Resolution Algorithm for Relative Constants + +At any given place in the code, let's define *cref* to be the first element of +the nesting if it is not empty, or `Object` otherwise. + +Without getting too much into the details, the resolution algorithm for relative +constant references goes like this: + +1. If the nesting is not empty the constant is looked up in its elements and in +order. The ancestors of those elements are ignored. + +2. If not found, then the algorithm walks up the ancestor chain of the cref. + +3. If not found, `const_missing` is invoked on the cref. The default +implementation of `const_missing` raises `NameError`, but it can be overridden. + +Rails autoloading **does not emulate this algorithm**, but its starting point is +the name of the constant to be autoloaded, and the cref. See more in [Relative +References](#autoloading-algorithms-relative-references). + +#### Resolution Algorithm for Qualified Constants + +Qualified constants look like this: + +```ruby +Billing::Invoice +``` + +`Billing::Invoice` is composed of two constants: `Billing` is relative and is +resolved using the algorithm of the previous section. + +INFO. Leading colons would make the first segment absolute rather than +relative: `::Billing::Invoice`. That would force `Billing` to be looked up +only as a top-level constant. + +`Invoice` on the other hand is qualified by `Billing` and we are going to see +its resolution next. Let's call *parent* to 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. + +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. + +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** +checked. + +Rails autoloading **does not emulate this algorithm**, but its starting point is +the name of the constant to be autoloaded, and the parent. See more in +[Qualified References](#autoloading-algorithms-qualified-references). + + +Vocabulary +---------- + +### Parent Namespaces + +Given a string with a constant path we define its *parent namespace* to be the +string that results from removing its rightmost segment. + +For example, the parent namespace of the string "A::B::C" is the string "A::B", +the parent namespace of "A::B" is "A", and the parent namespace of "A" is "". + +The interpretation of a parent namespace when thinking about classes and modules +is tricky though. Let's consider a module M named "A::B": + +* The parent namespace, "A", may not reflect nesting at a given spot. + +* The constant `A` may no longer exist, some code could have removed it from +`Object`. + +* If `A` exists, the class or module that was originally in `A` may not be there +anymore. For example, if after a constant removal there was another constant +assignment there would generally be a different object in there. + +* In such case, it could even happen that the reassigned `A` held a new class or +module called also "A"! + +* In the previous scenarios M would no longer be reachable through `A::B` but +the module object itself could still be alive somewhere and its name would +still be "A::B". + +The idea of a parent namespace is at the core of the autoloading algorithms +and helps explain and understand their motivation intuitively, but as you see +that metaphor leaks easily. Given an edge case to reason about, take always into +account that by "parent namespace" the guide means exactly that specific string +derivation. + +### Loading Mechanism + +Rails autoloads files with `Kernel#load` when `config.cache_classes` is false, +the default in development mode, and with `Kernel#require` otherwise, the +default in production mode. + +`Kernel#load` allows Rails to execute files more than once if [constant +reloading](#constant-reloading) is enabled. + +This guide uses the word "load" freely to mean a given file is interpreted, but +the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that +flag. + + +Autoloading Availability +------------------------ + +Rails is always able to autoload provided its environment is in place. For +example the `runner` command autoloads: + +``` +$ bin/rails runner 'p User.column_names' +["id", "email", "created_at", "updated_at"] +``` + +The console autoloads, the test suite autoloads, and of course the application +autoloads. + +By default, Rails eager loads the application files when it boots in production +mode, so most of the autoloading going on in development does not happen. But +autoloading may still be triggered during eager loading. + +For example, given + +```ruby +class BeachHouse < House +end +``` + +if `House` is still unknown when `app/models/beach_house.rb` is being eager +loaded, Rails autoloads it. + + +autoload_paths +-------------- + +As you probably know, when `require` gets a relative file name: + +```ruby +require 'erb' +``` + +Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby +iterates over all its directories and for each one of them checks whether they +have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds +any of them, the interpreter loads it and ends the search. Otherwise, it tries +again in the next directory of the list. If the list gets exhausted, `LoadError` +is raised. + +We are going to cover how constant autoloading works in more detail later, but +the idea is that when a constant like `Post` is hit and missing, if there's a +`post.rb` file for example in `app/models` Rails is going to find it, evaluate +it, and have `Post` defined as a side-effect. + +Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which +to look up `post.rb`. That collection is called `autoload_paths` and by +default it contains: + +* All subdirectories of `app` in the application and engines. For example, + `app/controllers`. They do not need to be the default ones, any custom + directories like `app/workers` belong automatically to `autoload_paths`. + +* Any existing second level directories called `app/*/concerns` in the + application and engines. + +* The directory `test/mailers/previews`. + +Also, this collection is configurable via `config.autoload_paths`. For example, +`lib` was in the list years ago, but no longer is. An application can opt-in +by adding this to `config/application.rb`: + +```ruby +config.autoload_paths += "#{Rails.root}/lib" +``` + +The value of `autoload_paths` can be inspected. In a just generated application +it is (edited): + +``` +$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' +.../app/assets +.../app/controllers +.../app/helpers +.../app/mailers +.../app/models +.../app/controllers/concerns +.../app/models/concerns +.../test/mailers/previews +``` + +INFO. `autoload_paths` is computed and cached during the initialization process. +The application needs to be restarted to reflect any changes in the directory +structure. + + +Autoloading Algorithms +---------------------- + +### Relative References + +A relative constant reference may appear in several places, for example, in + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +all three constant references are relative. + +#### Constants after the `class` and `module` Keywords + +Ruby performs a lookup for the constant that follows a `class` or `module` +keyword because it needs to know if the class or module is going to be created +or reopened. + +If the constant is not defined at that point it is not considered to be a +missing constant, autoloading is **not** triggered. + +So, in the previous example, if `PostsController` is not defined when the file +is interpreted Rails autoloading is not going to be triggered, Ruby will just +define the controller. + +#### Top-Level Constants + +On the contrary, if `ApplicationController` is unknown, the constant is +considered missing and an autoload is going to be attempted by Rails. + +In order to load `ApplicationController`, Rails iterates over `autoload_paths`. +First checks if `app/assets/application_controller.rb` exists. If it does not, +which is normally the case, it continues and finds +`app/controllers/application_controller.rb`. + +If the file defines the constant `ApplicationController` all is fine, otherwise +`LoadError` is raised: + +``` +unable to autoload constant ApplicationController, expected +<full path to application_controller.rb> to define it (LoadError) +``` + +INFO. Rails does not require the value of autoloaded constants to be a class or +module object. For example, if the file `app/models/max_clients.rb` defines +`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine. + +#### Namespaces + +Autoloading `ApplicationController` looks directly under the directories of +`autoload_paths` because the nesting in that spot is empty. The situation of +`Post` is different, the nesting in that line is `[PostsController]` and support +for namespaces comes into play. + +The basic idea is that given + +```ruby +module Admin + class BaseController < ApplicationController + @@all_roles = Role.all + end +end +``` + +to autoload `Role` we are going to check if it is defined in the current or +parent namespaces, one at a time. So, conceptually we want to try to autoload +any of + +``` +Admin::BaseController::Role +Admin::Role +Role +``` + +in that order. That's the idea. To do so, Rails looks in `autoload_paths` +respectively for file names like these: + +``` +admin/base_controller/role.rb +admin/role.rb +role.rb +``` + +modulus some additional directory lookups we are going to cover soon. + +INFO. `'Constant::Name'.underscore` gives the relative path without extension of +the file name where `Constant::Name` is expected to be defined. + +Let's see how Rails autoloads the `Post` constant in the `PostsController` +above assuming the application has a `Post` model defined in +`app/models/post.rb`. + +First it checks for `posts_controller/post.rb` in `autoload_paths`: + +``` +app/assets/posts_controller/post.rb +app/controllers/posts_controller/post.rb +app/helpers/posts_controller/post.rb +... +test/mailers/previews/posts_controller/post.rb +``` + +Since the lookup is exhausted without success, a similar search for a directory +is performed, we are going to see why in the [next section](#automatic-modules): + +``` +app/assets/posts_controller/post +app/controllers/posts_controller/post +app/helpers/posts_controller/post +... +test/mailers/previews/posts_controller/post +``` + +If all those attempts fail, then Rails starts the lookup again in the parent +namespace. In this case only the top-level remains: + +``` +app/assets/post.rb +app/controllers/post.rb +app/helpers/post.rb +app/mailers/post.rb +app/models/post.rb +``` + +A matching file is found in `app/models/post.rb`. The lookup stops there and the +file is loaded. If the file actually defines `Post` all is fine, otherwise +`LoadError` is raised. + +### Qualified References + +When a qualified constant is missing Rails does not look for it in the parent +namespaces. But there is a caveat: When a constant is missing, Rails is +unable to tell if the trigger was a relative reference or a qualified one. + +For example, consider + +```ruby +module Admin + User +end +``` + +and + +```ruby +Admin::User +``` + +If `User` is missing, in either case all Rails knows is that a constant called +"User" was missing in a module called "Admin". + +If there is a top-level `User` Ruby would resolve it in the former example, but +wouldn't in the latter. In general, Rails does not emulate the Ruby constant +resolution algorithms, but in this case it tries using the following heuristic: + +> If none of the parent namespaces of the class or module has the missing +> constant then Rails assumes the reference is relative. Otherwise qualified. + +For example, if this code triggers autoloading + +```ruby +Admin::User +``` + +and the `User` constant is already present in `Object`, it is not possible that +the situation is + +```ruby +module Admin + User +end +``` + +because otherwise Ruby would have resolved `User` and no autoloading would have +been triggered in the first place. Thus, Rails assumes a qualified reference and +considers the file `admin/user.rb` and directory `admin/user` to be the only +valid options. + +In practice, this works quite well as long as the nesting matches all parent +namespaces respectively and the constants that make the rule apply are known at +that time. + +However, autoloading happens on demand. If by chance the top-level `User` was +not yet loaded, then Rails assumes a relative reference by contract. + +Naming conflicts of this kind are rare in practice, but if one occurs, +`require_dependency` provides a solution by ensuring that the constant needed +to trigger the heuristic is defined in the conflicting place. + +### Automatic Modules + +When a module acts as a namespace, Rails does not require the application to +defines a file for it, a directory matching the namespace is enough. + +Suppose an application has a back office whose controllers are stored in +`app/controllers/admin`. If the `Admin` module is not yet loaded when +`Admin::UsersController` is hit, Rails needs first to autoload the constant +`Admin`. + +If `autoload_paths` has a file called `admin.rb` Rails is going to load that +one, but if there's no such file and a directory called `admin` is found, Rails +creates an empty module and assigns it to the `Admin` constant on the fly. + +### Generic Procedure + +Relative references are reported to be missing in the cref where they were hit, +and qualified references are reported to be missing in their parent. (See +[Resolution Algorithm for Relative +Constants](#resolution-algorithm-for-relative-constants) at the beginning of +this guide for the definition of *cref*, and [Resolution Algorithm for Qualified +Constants](#resolution-algorithm-for-qualified-constants) for the definition of +*parent*.) + +The procedure to autoload constant `C` in an arbitrary situation is as follows: + +``` +if the class or module in which C is missing is Object + let ns = '' +else + let M = the class or module in which C is missing + + if M is anonymous + let ns = '' + else + let ns = M.name + end +end + +loop do + # Look for a regular file. + for dir in autoload_paths + if the file "#{dir}/#{ns.underscore}/c.rb" exists + load/require "#{dir}/#{ns.underscore}/c.rb" + + if C is now defined + return + else + raise LoadError + end + end + end + + # Look for an automatic module. + for dir in autoload_paths + if the directory "#{dir}/#{ns.underscore}/c" exists + if ns is an empty string + let C = Module.new in Object and return + else + let C = Module.new in ns.constantize and return + end + end + end + + if ns is empty + # We reached the top-level without finding the constant. + raise NameError + else + if C exists in any of the parent namespaces + # Qualified constants heuristic. + raise NameError + else + # Try again in the parent namespace. + let ns = the parent namespace of ns and retry + end + end +end +``` + + +require_dependency +------------------ + +Constant autoloading is triggered on demand and therefore code that uses a +certain constant may have it already defined or may trigger an autoload. That +depends on the execution path and it may vary between runs. + +There are times, however, in which you want to make sure a certain constant is +known when the execution reaches some code. `require_dependency` provides a way +to load a file using the current [loading mechanism](#loading-mechanism), and +keeping track of constants defined in that file as if they were autoloaded to +have them reloaded as needed. + +`require_dependency` is rarely needed, but see a couple of use-cases in +[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't +Triggered](#when-constants-aren-t-missed). + +WARNING. Unlike autoloading, `require_dependency` does not expect the file to +define any particular constant. Exploiting this behavior would be a bad practice +though, file and constant paths should match. + + +Constant Reloading +------------------ + +When `config.cache_classes` is false Rails is able to reload autoloaded +constants. + +For example, in you're in a console session and edit some file behind the +scenes, the code can be reloaded with the `reload!` command: + +``` +> reload! +``` + +When the application runs, code is reloaded when something relevant to this +logic changes. In order to do that, Rails monitors a number of things: + +* `config/routes.rb`. + +* Locales. + +* Ruby files under `autoload_paths`. + +* `db/schema.rb` and `db/structure.sql`. + +If anything in there changes, there is a middleware that detects it and reloads +the code. + +Autoloading keeps track of autoloaded constants. Reloading is implemented by +removing them all from their respective classes and modules using +`Module#remove_const`. That way, when the code goes on, those constants are +going to be unknown again, and files reloaded on demand. + +INFO. This is an all-or-nothing operation, Rails does not attempt to reload only +what changed since dependencies between classes makes that really tricky. +Instead, everything is wiped. + + +Module#autoload isn't Involved +------------------------------ + +`Module#autoload` provides a lazy way to load constants that is fully integrated +with the Ruby constant lookup algorithms, dynamic constant API, etc. It is quite +transparent. + +Rails internals make extensive use of it to defer as much work as possible from +the boot process. But constant autoloading in Rails is **not** implemented with +`Module#autoload`. + +One possible implementation based on `Module#autoload` would be to walk the +application tree and issue `autoload` calls that map existing file names to +their conventional constant name. + +There are a number of reasons that prevent Rails from using that implementation. + +For example, `Module#autoload` is only capable of loading files using `require`, +so reloading would not be possible. Not only that, it uses an internal `require` +which is not `Kernel#require`. + +Then, it provides no way to remove declarations in case a file is deleted. If a +constant gets removed with `Module#remove_const` its `autoload` is not triggered +again. Also, it doesn't support qualified names, so files with namespaces should +be interpreted during the walk tree to install their own `autoload` calls, but +those files could have constant references not yet configured. + +An implementation based on `Module#autoload` would be awesome but, as you see, +at least as of today it is not possible. Constant autoloading in Rails is +implemented with `Module#const_missing`, and that's why it has its own contract, +documented in this guide. + + +Common Gotchas +-------------- + +### Nesting and Qualified Constants + +Let's consider + +```ruby +module Admin + class UsersController < ApplicationController + def index + @users = User.all + end + end +end +``` + +and + +```ruby +class Admin::UsersController < ApplicationController + def index + @users = User.all + end +end +``` + +To resolve `User` Ruby checks `Admin` in the former case, but it does not in +the latter because it does not belong to the nesting. (See [Nesting](#nesting) +and [Resolution Algorithms](#resolution-algorithms).) + +Unfortunately Rails autoloading does not know the nesting in the spot where the +constant was missing and so it is not able to act as Ruby would. In particular, +`Admin::User` will get autoloaded in either case. + +Albeit qualified constants with `class` and `module` keywords may technically +work with autoloading in some cases, it is preferable to use relative constants +instead: + +```ruby +module Admin + class UsersController < ApplicationController + def index + @users = User.all + end + end +end +``` + +### Autoloading and STI + +Single Table Inheritance (STI) is a feature of Active Record that enables +storing a hierarchy of models in one single table. The API of such models is +aware of the hierarchy and encapsulates some common needs. For example, given +these classes: + +```ruby +# app/models/polygon.rb +class Polygon < ActiveRecord::Base +end + +# app/models/triangle.rb +class Triangle < Polygon +end + +# app/models/rectangle.rb +class Rectangle < Polygon +end +``` + +`Triangle.create` creates a row that represents a triangle, and +`Rectangle.create` creates a row that represents a rectangle. If `id` is the +ID of an existing record, `Polygon.find(id)` returns an object of the correct +type. + +Methods that operate on collections are also aware of the hierarchy. For +example, `Polygon.all` returns all the records of the table, because all +rectangles and triangles are polygons. Active Record takes care of returning +instances of their corresponding class in the result set. + +Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle +and `Rectangle` has not yet been loaded, Active Record autoloads it and the +record is correctly instantiated. + +All good, but if instead of performing queries based on the root class we need +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. + +`Rectangle.all` only loads rectangles by adding a type constraint to the query: + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle") +``` + +Let’s introduce now a subclass of `Rectangle`: + +```ruby +# app/models/square.rb +class Square < Rectangle +end +``` + +`Rectangle.all` should now return rectangles **and** squares: + +```sql +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` +exists at all? + +Even if the file `app/models/square.rb` exists and defines the `Square` class, +if no code yet used that class, `Rectangle.all` issues the query + +```sql +SELECT "polygons".* FROM "polygons" +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: + +```ruby +# app/models/polygon.rb +class Polygon < ActiveRecord::Base +end +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. + +### Autoloading and `require` + +Files defining constants to be autoloaded should never be `require`d: + +```ruby +require 'user' # DO NOT DO THIS + +class UsersController < ApplicationController + ... +end +``` + +There are two possible gotchas here in development mode: + +1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb` +runs again because `load` does not update `$LOADED_FEATURES`. + +2. If the `require` runs first Rails does not mark `User` as an autoloaded +constant and changes to `app/models/user.rb` aren't reloaded. + +Just follow the flow and use constant autoloading always, never mix +autoloading and `require`. As a last resort, if some file absolutely needs to +load a certain file use `require_dependency` to play nice with constant +autoloading. This option is rarely needed in practice, though. + +Of course, using `require` in autoloaded files to load ordinary 3rd party +libraries is fine, and Rails is able to distinguish their constants, they are +not marked as autoloaded. + +### Autoloading and Initializers + +Consider this assignment in `config/initializers/set_auth_service.rb`: + +```ruby +AUTH_SERVICE = if Rails.env.production? + RealAuthService +else + MockedAuthService +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 +we do some requests, change its implementation, and hit the application again. +To our surprise the changes are not reflected. Why? + +As [we saw earlier](#constant-reloading), Rails removes autoloaded constants, +but `AUTH_SERVICE` stores the original class object. Stale, non-reachable +using the original constant, but perfectly functional. + +The following code summarizes the situation: + +```ruby +class C + def quack + 'quack!' + end +end + +X = C +Object.instance_eval { remove_const(:C) } +X.new.quack # => quack! +X.name # => C +C # => uninitialized constant C (NameError) +``` + +Because of that, it is not a good idea to autoload constants on application +initialization. + +In the case above we could implement a dynamic access point: + +```ruby +# app/models/auth_service.rb +class AuthService + if Rails.env.production? + def self.instance + RealAuthService + end + else + def self.instance + MockedAuthService + end + end +end +``` + +and have the application use `AuthService.instance` instead. `AuthService` +would be loaded on demand and be autoload-friendly. + +### `require_dependency` and Initializers + +As we saw before, `require_dependency` loads files in an autoloading-friendly +way. Normally, though, such a call does not make sense in an initializer. + +One could think about doing some [`require_dependency`](#require-dependency) +calls in an initializer to make sure certain constants are loaded upfront, for +example as an attempt to address the [gotcha with STIs](#autoloading-and-sti). + +Problem is, in development mode [autoloaded constants are wiped](#constant-reloading) +if there is any relevant change in the file system. If that happens then +we are in the very same situation the initializer wanted to avoid! + +Calls to `require_dependency` have to be strategically written in autoloaded +spots. + +### When Constants aren't Missed + +#### Relative References + +Let's consider a flight simulator. The application has a default flight model + +```ruby +# app/models/flight_model.rb +class FlightModel +end +``` + +that can be overridden by each airplane, for instance + +```ruby +# app/models/bell_x1/flight_model.rb +module BellX1 + class FlightModel < FlightModel + end +end + +# app/models/bell_x1/aircraft.rb +module BellX1 + class Aircraft + def initialize + @flight_model = FlightModel.new + end + end +end +``` + +The initializer wants to create a `BellX1::FlightModel` and nesting has +`BellX1`, that looks good. But if the default flight model is loaded and the +one for the Bell-X1 is not, the interpreter is able to resolve the top-level +`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`. + +That code depends on the execution path. + +These kind of ambiguities can often be resolved using qualified constants: + +```ruby +module BellX1 + class Plane + def flight_model + @flight_model ||= BellX1::FlightModel.new + end + end +end +``` + +Also, `require_dependency` is a solution: + +```ruby +require_dependency 'bell_x1/flight_model' + +module BellX1 + class Plane + def flight_model + @flight_model ||= FlightModel.new + end + end +end +``` + +#### Qualified References + +Given + +```ruby +# app/models/hotel.rb +class Hotel +end + +# app/models/image.rb +class Image +end + +# app/models/hotel/image.rb +class Hotel + class Image < Image + end +end +``` + +the expression `Hotel::Image` is ambiguous because it depends on the execution +path. + +As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks +up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has +been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image` +in `Hotel`, but it does in `Object`: + +``` +$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null +Image # NOT Hotel::Image! +``` + +The code evaluating `Hotel::Image` needs to make sure +`app/models/hotel/image.rb` has been loaded, possibly with +`require_dependency`. + +In these cases the interpreter issues a warning though: + +``` +warning: toplevel constant Image referenced by Hotel::Image +``` + +This surprising constant resolution can be observed with any qualifying class: + +``` +2.1.5 :001 > String::Array +(irb):1: warning: toplevel constant Array referenced by String::Array + => Array +``` + +WARNING. To find this gotcha the qualifying namespace has to be a class, +`Object` is not an ancestor of modules. + +### Autoloading within Singleton Classes + +Let's suppose we have these class definitions: + +```ruby +# app/models/hotel/services.rb +module Hotel + class Services + end +end + +# app/models/hotel/geo_location.rb +module Hotel + class GeoLocation + class << self + Services + end + end +end +``` + +If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb` +is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the +nesting when the singleton class of `Hotel::GeoLocation` is opened. + +But if `Hotel::Services` is not known, Rails is not able to autoload it, the +application raises `NameError`. + +The reason is that autoloading is triggered for the singleton class, which is +anonymous, and as [we saw before](#generic-procedure), Rails only checks the +top-level namespace in that edge case. + +An easy solution to this caveat is to qualify the constant: + +```ruby +module Hotel + class GeoLocation + class << self + Hotel::Services + end + end +end +``` + +### Autoloading in `BasicObject` + +Direct descendants of `BasicObject` do not have `Object` among their ancestors +and cannot resolve top-level constants: + +```ruby +class C < BasicObject + String # NameError: uninitialized constant C::String +end +``` + +When autoloading is involved that plot has a twist. Let's consider: + +```ruby +class C < BasicObject + def user + User # WRONG + end +end +``` + +Since Rails checks the top-level namespace `User` gets autoloaded just fine the +first time the `user` method is invoked. You only get the exception if the +`User` constant is known at that point, in particular in a *second* call to +`user`: + +```ruby +c = C.new +c.user # surprisingly fine, User +c.user # NameError: uninitialized constant C::User +``` + +because it detects that a parent namespace already has the constant (see [Qualified +References](#autoloading-algorithms-qualified-references).) + +As with pure Ruby, within the body of a direct descendant of `BasicObject` use +always absolute constant paths: + +```ruby +class C < BasicObject + ::String # RIGHT + + def user + ::User # RIGHT + end +end +``` diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 0d45e5fb28..0fa20f7ccf 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Caching with Rails: An overview =============================== @@ -28,15 +30,15 @@ config.action_controller.perform_caching = true ### Page Caching -Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. +Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or NGINX), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Action Caching Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy. -INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Fragment Caching @@ -105,7 +107,7 @@ This method generates a cache key that depends on all products and can be used i <% end %> ``` -If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless` +If you want to cache a fragment under certain conditions, you can use `cache_if` or `cache_unless` ```erb <% cache_if (condition, cache_key_for_products) do %> @@ -140,6 +142,26 @@ You can also combine the two schemes which is called "Russian Doll Caching": It's called "Russian Doll Caching" because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment. +### Low-Level Caching + +Sometimes you need to cache a particular value or query result, instead of caching view fragments. Rails caching mechanism works great for storing __any__ kind of information. + +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, the result of the block will be cached to the given key and the result is returned. + +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 < ActiveRecord::Base + def competing_price + Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do + Competitor::API.find_price(id) + end + end +end +``` + +NOTE: Notice that in this example we used `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 Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again. @@ -162,10 +184,14 @@ class ProductsController < ApplicationController end ``` +The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory. + +However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching. + Cache Stores ------------ -Rails provides different stores for the cached data created by <b>action</b> and <b>fragment</b> caches. +Rails provides different stores for the cached data created by **action** and **fragment** caches. TIP: Page caches are always stored on disk. @@ -333,12 +359,17 @@ Instead of an options hash, you can also simply pass in a model, Rails will use class ProductsController < ApplicationController def show @product = Product.find(params[:id]) - respond_with(@product) if stale?(@product) + + if stale?(@product) + respond_to do |wants| + # ... normal response processing + end + end end end ``` -If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using respond_to or calling render yourself) then you've got an easy helper in fresh_when: +If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using `respond_to` or calling render yourself) then you've got an easy helper in `fresh_when`: ```ruby class ProductsController < ApplicationController diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 3b80faec7f..78f26ccefa 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Rails Command Line ====================== @@ -7,7 +9,6 @@ After reading this guide, you will know: * How to generate models, controllers, database migrations, and unit tests. * How to start a development server. * How to experiment with objects through an interactive shell. -* How to profile and benchmark your new creation. -------------------------------------------------------------------------------- @@ -25,7 +26,7 @@ There are a few commands that are absolutely critical to your everyday usage of * `rails dbconsole` * `rails new app_name` -All commands can run with ```-h or --help``` to list more information. +All commands can run with `-h` or `--help` to list more information. Let's create a simple Rails application to step through each of these commands in context. @@ -60,9 +61,9 @@ With no further work, `rails server` will run our new shiny Rails app: ```bash $ cd commandsapp -$ rails server +$ bin/rails server => Booting WEBrick -=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000 +=> Rails 5.0.0 application starting in development on http://localhost:3000 => Call with -d to detach => Ctrl-C to shutdown server [2013-08-07 02:00:01] INFO WEBrick 1.3.1 @@ -77,10 +78,10 @@ INFO: You can also use the alias "s" to start the server: `rails s`. The server can be run on a different port using the `-p` option. The default development environment can be changed using `-e`. ```bash -$ rails server -e production -p 4000 +$ bin/rails server -e production -p 4000 ``` -The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option. +The `-b` option binds Rails to the specified IP, by default it is localhost. You can run a server as a daemon by passing a `-d` option. ### `rails generate` @@ -89,7 +90,7 @@ The `rails generate` command uses templates to create a whole lot of things. Run INFO: You can also use the alias "g" to invoke the generator command: `rails g`. ```bash -$ rails generate +$ bin/rails generate Usage: rails generate GENERATOR [args] [options] ... @@ -114,7 +115,7 @@ Let's make our own controller with the controller generator. But what command sh INFO: All Rails console utilities have help text. As with most *nix utilities, you can try adding `--help` or `-h` to the end, for example `rails server --help`. ```bash -$ rails generate controller +$ bin/rails generate controller Usage: rails generate controller NAME [action action] [options] ... @@ -123,25 +124,24 @@ Usage: rails generate controller NAME [action action] [options] Description: ... - To create a controller within a module, specify the controller name as a - path like 'parent_module/controller_name'. + To create a controller within a module, specify the controller name as a path like 'parent_module/controller_name'. ... Example: - `rails generate controller CreditCard open debit credit close` + `rails generate controller CreditCards open debit credit close` - Credit card controller with URLs like /credit_card/debit. - Controller: app/controllers/credit_card_controller.rb - Test: test/controllers/credit_card_controller_test.rb - Views: app/views/credit_card/debit.html.erb [...] - Helper: app/helpers/credit_card_helper.rb + Credit card controller with URLs like /credit_cards/debit. + Controller: app/controllers/credit_cards_controller.rb + Test: test/controllers/credit_cards_controller_test.rb + Views: app/views/credit_cards/debit.html.erb [...] + Helper: app/helpers/credit_cards_helper.rb ``` The controller generator is expecting parameters in the form of `generate controller ControllerName action1 action2`. Let's make a `Greetings` controller with an action of **hello**, which will say something nice to us. ```bash -$ rails generate controller Greetings hello +$ bin/rails generate controller Greetings hello create app/controllers/greetings_controller.rb route get "greetings/hello" invoke erb @@ -151,13 +151,11 @@ $ rails generate controller Greetings hello create test/controllers/greetings_controller_test.rb invoke helper create app/helpers/greetings_helper.rb - invoke test_unit - create test/helpers/greetings_helper_test.rb invoke assets invoke coffee - create app/assets/javascripts/greetings.js.coffee + create app/assets/javascripts/greetings.coffee invoke scss - create app/assets/stylesheets/greetings.css.scss + create app/assets/stylesheets/greetings.scss ``` What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a JavaScript file and a stylesheet file. @@ -182,7 +180,7 @@ Then the view, to display our message (in `app/views/greetings/hello.html.erb`): Fire up your server using `rails server`. ```bash -$ rails server +$ bin/rails server => Booting WEBrick... ``` @@ -193,7 +191,7 @@ INFO: With a normal, plain-old Rails application, your URLs will generally follo Rails comes with a generator for data models too. ```bash -$ rails generate model +$ bin/rails generate model Usage: rails generate model NAME [field[:type][:index] field[:type][:index]] [options] @@ -216,7 +214,7 @@ But instead of generating a model directly (which we'll be doing later), let's s We will set up a simple resource called "HighScore" that will keep track of our highest score on video games we play. ```bash -$ rails generate scaffold HighScore game:string score:integer +$ bin/rails generate scaffold HighScore game:string score:integer invoke active_record create db/migrate/20130717151933_create_high_scores.rb create app/models/high_score.rb @@ -238,26 +236,24 @@ $ rails generate scaffold HighScore game:string score:integer create test/controllers/high_scores_controller_test.rb invoke helper create app/helpers/high_scores_helper.rb - invoke test_unit - create test/helpers/high_scores_helper_test.rb invoke jbuilder create app/views/high_scores/index.json.jbuilder create app/views/high_scores/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/high_scores.js.coffee + create app/assets/javascripts/high_scores.coffee invoke scss - create app/assets/stylesheets/high_scores.css.scss + create app/assets/stylesheets/high_scores.scss invoke scss - identical app/assets/stylesheets/scaffolds.css.scss + identical app/assets/stylesheets/scaffolds.scss ``` The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. -The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. +The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. ```bash -$ rake db:migrate +$ bin/rake db:migrate == CreateHighScores: migrating =============================================== -- create_table(:high_scores) -> 0.0017s @@ -269,7 +265,7 @@ INFO: Let's talk about unit tests. Unit tests are code that tests and makes asse Let's see the interface Rails created for us. ```bash -$ rails server +$ bin/rails server ``` Go to your browser and open [http://localhost:3000/high_scores](http://localhost:3000/high_scores), now we can create new high scores (55,160 on Space Invaders!) @@ -283,18 +279,43 @@ 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 -$ rails console staging +$ bin/rails console staging ``` If you wish to test out some code without changing any data, you can do that by invoking `rails console --sandbox`. ```bash -$ rails console --sandbox -Loading development environment in sandbox (Rails 4.0.0) +$ bin/rails console --sandbox +Loading development environment in sandbox (Rails 5.0.0) Any modifications you make will be rolled back on exit irb(main):001:0> ``` +#### The app and helper objects + +Inside the `rails console` you have access to the `app` and `helper` instances. + +With the `app` method you can access url and path helpers, as well as do requests. + +```bash +>> app.root_path +=> "/" + +>> app.get _ +Started GET "/" for 127.0.0.1 at 2014-06-19 10:41:57 -0300 +... +``` + +With the `helper` method it is possible to access Rails and your application's helpers. + +```bash +>> helper.time_ago_in_words 30.days.ago +=> "about 1 month" + +>> helper.my_custom_helper +=> "my custom helper" +``` + ### `rails dbconsole` `rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. @@ -306,7 +327,7 @@ INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`. `runner` runs Ruby code in the context of Rails non-interactively. For instance: ```bash -$ rails runner "Model.long_running_method" +$ bin/rails runner "Model.long_running_method" ``` INFO: You can also use the alias "r" to invoke the runner: `rails r`. @@ -314,7 +335,7 @@ INFO: You can also use the alias "r" to invoke the runner: `rails r`. You can specify the environment in which the `runner` command should operate using the `-e` switch. ```bash -$ rails runner -e staging "Model.long_running_method" +$ bin/rails runner -e staging "Model.long_running_method" ``` ### `rails destroy` @@ -324,7 +345,7 @@ Think of `destroy` as the opposite of `generate`. It'll figure out what generate INFO: You can also use the alias "d" to invoke the destroy command: `rails d`. ```bash -$ rails generate model Oops +$ bin/rails generate model Oops invoke active_record create db/migrate/20120528062523_create_oops.rb create app/models/oops.rb @@ -333,7 +354,7 @@ $ rails generate model Oops create test/fixtures/oops.yml ``` ```bash -$ rails destroy model Oops +$ bin/rails destroy model Oops invoke active_record remove db/migrate/20120528062523_create_oops.rb remove app/models/oops.rb @@ -349,41 +370,36 @@ Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'mak You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need. -To get the full backtrace for running rake task you can pass the option -```--trace``` to command line, for example ```rake db:create --trace```. +To get the full backtrace for running rake task you can pass the option `--trace` to command line, for example `rake db:create --trace`. ```bash -$ rake --tasks +$ bin/rake --tasks rake about # List versions of all Rails frameworks and the environment -rake assets:clean # Remove compiled assets +rake assets:clean # Remove old compiled assets +rake assets:clobber # Remove compiled assets rake assets:precompile # Compile all the assets named in config.assets.precompile rake db:create # Create the database from config/database.yml for the current Rails.env ... rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development) rake middleware # Prints out your Rack middleware stack ... -rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) -rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids +rake tmp:clear # Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear) +rake tmp:create # Creates tmp directories for cache, sockets, and pids ``` -INFO: You can also use ```rake -T``` to get the list of tasks. +INFO: You can also use `rake -T` to get the list of tasks. ### `about` `rake about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. ```bash -$ rake about +$ bin/rake about About your application's environment -Ruby version 1.9.3 (x86_64-linux) -RubyGems version 1.3.6 -Rack version 1.3 -Rails version 4.1.0 +Rails version 5.0.0 +Ruby version 2.2.0 (x86_64-linux) +RubyGems version 2.4.5 +Rack version 1.6 JavaScript Runtime Node.js (V8) -Active Record version 4.1.0 -Action Pack version 4.1.0 -Action View version 4.1.0 -Action Mailer version 4.1.0 -Active Support version 4.1.0 Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<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, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development @@ -393,7 +409,9 @@ Database schema version 20110805173523 ### `assets` -You can precompile the assets in `app/assets` using `rake assets:precompile` and remove those compiled assets using `rake assets:clean`. +You can precompile the assets in `app/assets` using `rake assets:precompile`, and remove older compiled assets using `rake assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built. + +If you want to clear `public/assets` completely, you can use `rake assets:clobber`. ### `db` @@ -411,10 +429,10 @@ The `doc:` namespace has the tools to generate documentation for your app, API d ### `notes` -`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.erb`, `.haml` and `.slim` for both default and custom annotations. +`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations. ```bash -$ rake notes +$ bin/rake notes (in /home/foobar/commandsapp) app/controllers/admin/users_controller.rb: * [ 20] [TODO] any other way to do this? @@ -425,10 +443,16 @@ app/models/school.rb: * [ 17] [FIXME] ``` +You can add support for new file extensions using `config.annotations.register_extensions` option, which receives a list of the extensions with its corresponding regex to match it up. + +```ruby +config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } +``` + If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name. ```bash -$ rake notes:fixme +$ bin/rake notes:fixme (in /home/foobar/commandsapp) app/controllers/admin/users_controller.rb: * [132] high priority for next deploy @@ -440,9 +464,9 @@ app/models/school.rb: You can also use custom annotations in your code and list them using `rake notes:custom` by specifying the annotation using an environment variable `ANNOTATION`. ```bash -$ rake notes:custom ANNOTATION=BUG +$ bin/rake notes:custom ANNOTATION=BUG (in /home/foobar/commandsapp) -app/models/post.rb: +app/models/article.rb: * [ 23] Have to fix this one before pushing! ``` @@ -452,7 +476,7 @@ By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `tes ```bash $ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor' -$ rake notes +$ bin/rake notes (in /home/foobar/commandsapp) app/models/user.rb: * [ 35] [FIXME] User should have a subscription at this point @@ -472,15 +496,14 @@ Rails comes with a test suite called Minitest. Rails owes its stability to the u ### `tmp` -The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. +The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like process id files and cached actions. The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory: * `rake tmp:cache:clear` clears `tmp/cache`. -* `rake tmp:sessions:clear` clears `tmp/sessions`. * `rake tmp:sockets:clear` clears `tmp/sockets`. -* `rake tmp:clear` clears all the three: cache, sessions and sockets. -* `rake tmp:create` creates tmp directories for sessions, cache, sockets, and pids. +* `rake tmp:clear` clears all cache and sockets files. +* `rake tmp:create` creates tmp directories for cache, sockets and pids. ### Miscellaneous @@ -524,9 +547,9 @@ end Invocation of the tasks will look like: ```bash -rake task_name -rake "task_name[value 1]" # entire argument string should be quoted -rake db:nothing +$ bin/rake task_name +$ bin/rake "task_name[value 1]" # entire argument string should be quoted +$ bin/rake db:nothing ``` NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the `environment` task, which will load your application code. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 412aecadd5..cc3d840271 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Configuring Rails Applications ============================== @@ -56,13 +58,13 @@ These configuration methods are to be called on a `Rails::Railtie` object, such end ``` -* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`. +* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`. * `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array. * `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`. -* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. Can also be enabled with `threadsafe!`. +* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. * `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`. @@ -86,7 +88,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`). end ``` -* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. This flag is set to false by `config.threadsafe!`. +* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. * `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. @@ -108,19 +110,19 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. -* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`. +* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments. The available log levels are: :debug, :info, :warn, :error, :fatal, and :unknown. -* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. +* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. -* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode. +* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`. * `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below. * `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. -* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`. +* `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`. -* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. +* `config.serve_static_files` configures Rails to serve static files. 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. * `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: @@ -137,7 +139,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `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.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.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`. @@ -151,7 +153,9 @@ pipeline is enabled. It is set to true by default. * `config.assets.prefix` defines the prefix where assets are served from. Defaults to `/assets`. -* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb`. +* `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-<random>.json` in the `config.assets.prefix` directory within the public folder. + +* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb` and `development.rb`. * `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`. @@ -195,7 +199,7 @@ The full set of methods that can be used in this block are as follows: Every Rails application comes with a standard set of middleware which it uses in this order in the development environment: * `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`. -* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_assets` is `false`. +* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`. * `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. @@ -212,7 +216,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value. * `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. -* `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so. +* `Rack::Head` converts HEAD requests to GET requests and serves them as so. Besides these usual middleware, you can add your own by using the `config.middleware.use` method: @@ -223,13 +227,13 @@ config.middleware.use Magical::Unicorns This will put the `Magical::Unicorns` middleware on the end of the stack. You can use `insert_before` if you wish to add a middleware before another. ```ruby -config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_before Rack::Head, Magical::Unicorns ``` There's also `insert_after` which will insert a middleware after another: ```ruby -config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_after Rack::Head, Magical::Unicorns ``` Middlewares can also be completely swapped out and replaced with others: @@ -274,7 +278,7 @@ All these configuration options are delegated to the `I18n` library. * `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` for Rails, although Active Record defaults to `:local` when used outside of Rails. +* `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`. * `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements. @@ -288,10 +292,14 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.partial_writes` is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty). Note that when using partial writes, you should also use optimistic locking `config.active_record.lock_optimistically` since concurrent updates may write attributes based on a possibly stale read state. The default value is `true`. -* `config.active_record.attribute_types_cached_by_default` sets the attribute types that `ActiveRecord::AttributeMethods` will cache by default on reads. The default is `[:datetime, :timestamp, :time, :date]`. - * `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true. +* `config.active_record.dump_schema_after_migration` is a flag which + controls whether or not schema dump should happen (`db/schema.rb` or + `db/structure.sql`) when you run migrations. This is set to false in + `config/environments/production.rb` which is generated by Rails. The + default value is true if this configuration is not set. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. @@ -312,6 +320,8 @@ The schema dumper adds one additional configuration option: * `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8". +* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`. + * `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging. * `config.action_controller.request_forgery_protection_token` sets the token parameter name for RequestForgery. Calling `protect_from_forgery` sets it to `:authenticity_token` by default. @@ -324,6 +334,8 @@ The schema dumper adds one additional configuration option: * `config.action_controller.action_on_unpermitted_parameters` enables logging or raising an exception if parameters that are not explicitly permitted are found. Set to `:log` or `:raise` to enable. The default value is `:log` in development and test environments, and `false` in all other environments. +* `config.action_controller.always_permitted_parameters` sets a list of whitelisted parameters that are permitted by default. The default values are `['controller', 'action']`. + ### Configuring Action Dispatch * `config.action_dispatch.session_store` sets the name of the store for session data. The default is `:cookie_store`; other valid options include `:active_record_store`, `:mem_cache_store` or the name of your own custom class. @@ -352,6 +364,34 @@ value. Defaults to `'encrypted cookie'`. * `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. +* `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) + for more information. It defaults to true. + +* `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as: + + ```ruby + config.action_dispatch.rescue_responses = { + 'ActionController::RoutingError' => :not_found, + 'AbstractController::ActionNotFound' => :not_found, + 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, + 'ActionController::NotImplemented' => :not_implemented, + 'ActionController::UnknownFormat' => :not_acceptable, + 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, + 'ActionController::BadRequest' => :bad_request, + 'ActionController::ParameterMissing' => :bad_request, + 'ActiveRecord::RecordNotFound' => :not_found, + 'ActiveRecord::StaleObjectError' => :conflict, + 'ActiveRecord::RecordInvalid' => :unprocessable_entity, + 'ActiveRecord::RecordNotSaved' => :unprocessable_entity + } + ``` + + Any exceptions that are not configured will be mapped to 500 Internal Server Error. + * `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`. @@ -374,17 +414,19 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. * `config.action_view.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to `nil` to disable logging. -* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`. See the [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/) for more information. +* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`, which turns on trimming of tail spaces and newline when using `<%= -%>` or `<%= =%>`. See the [Erubis documentation](http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-trimspaces) for more information. * `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true` -* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::PostsController` which renders this template: +* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::ArticlesController` which renders this template: ```erb - <%= render @post %> + <%= render @article %> ``` - The default setting is `true`, which uses the partial at `/admin/posts/_post.erb`. Setting the value to `false` would render `/posts/_post.erb`, which is the same behavior as rendering from a non-namespaced controller such as `PostsController`. + The default setting is `true`, which uses the partial at `/admin/articles/_article.erb`. Setting the value to `false` would render `/articles/_article.erb`, which is the same behavior as rendering from a non-namespaced controller such as `ArticlesController`. + +* `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations ### Configuring Action Mailer @@ -406,17 +448,25 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true. -* `config.action_mailer.delivery_method` defines the delivery method. The allowed values are `:smtp` (default), `:sendmail`, and `:test`. +* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration) for more info. * `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. * `config.action_mailer.default_options` configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to: ```ruby - :mime_version => "1.0", - :charset => "UTF-8", - :content_type => "text/plain", - :parts_order => [ "text/plain", "text/enriched", "text/html" ] + mime_version: "1.0", + charset: "UTF-8", + content_type: "text/plain", + parts_order: ["text/plain", "text/enriched", "text/html"] + ``` + + Assign a hash to set additional options: + + ```ruby + config.action_mailer.default_options = { + from: "noreply@example.com" + } ``` * `config.action_mailer.observers` registers observers which will be notified when mail is delivered. @@ -431,16 +481,34 @@ There are a number of settings available on `config.action_mailer`: config.action_mailer.interceptors = ["MailInterceptor"] ``` +* `config.action_mailer.preview_path` specifies the location of mailer previews. + + ```ruby + config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" + ``` + +* `config.action_mailer.show_previews` enable or disable mailer previews. By default this is `true` in development. + + ```ruby + config.action_mailer.show_previews = false + ``` + ### Configuring Active Support There are a few configuration options available in Active Support: * `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded. +* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:sorted` and `:random`. Currently defaults to `:sorted`. In Rails 5.0, the default will be changed to `:random` instead. + * `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `false`. * `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`. +* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`. + +* `config.active_support.halt_callback_chains_on_return_false` specifies whether ActiveRecord, ActiveModel and ActiveModel::Validations callback chains can be halted by returning `false` in a 'before' callback. Defaults to `true`. + * `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. @@ -451,7 +519,6 @@ There are a few configuration options available in Active Support: * `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings. -* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. ### Configuring a Database @@ -531,7 +598,7 @@ development: $ echo $DATABASE_URL postgresql://localhost/my_database -$ rails runner 'puts ActiveRecord::Base.connections' +$ bin/rails runner 'puts ActiveRecord::Base.configurations' {"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}} ``` @@ -548,24 +615,24 @@ development: $ echo $DATABASE_URL postgresql://localhost/my_database -$ rails runner 'puts ActiveRecord::Base.connections' +$ bin/rails runner 'puts ActiveRecord::Base.configurations' {"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}} ``` Since pool is not in the `ENV['DATABASE_URL']` provided connection information its information is merged in. Since `adapter` is duplicate, the `ENV['DATABASE_URL']` connection information wins. -The only way to explicitly not use the connection information in `ENV['DATABASE_URL']` is to specify an explicit URL connectinon using the `"url"` sub key: +The only way to explicitly not use the connection information in `ENV['DATABASE_URL']` is to specify an explicit URL connection using the `"url"` sub key: ``` $ cat config/database.yml development: - url: sqlite3://localhost/NOT_my_database + url: sqlite3:NOT_my_database $ echo $DATABASE_URL postgresql://localhost/my_database -$ rails runner 'puts ActiveRecord::Base.connections' -{"development"=>{"adapter"=>"sqlite3", "host"=>"localhost", "database"=>"NOT_my_database"}} +$ bin/rails runner 'puts ActiveRecord::Base.configurations' +{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}} ``` Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name. @@ -623,11 +690,9 @@ development: encoding: unicode database: blog_development pool: 5 - username: blog - password: ``` -Prepared Statements can be disabled thus: +Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`: ```yaml production: @@ -635,6 +700,16 @@ production: prepared_statements: false ``` +If enabled, Active Record will create up to `1000` prepared statements per database connection by default. To modify this behavior you can set `statement_limit` to a different value: + +``` +production: + adapter: postgresql + statement_limit: 200 +``` + +The more prepared statements in use: the more memory your database will require. If your PostgreSQL database is hitting memory limits, try lowering `statement_limit` or disabling prepared statements. + #### Configuring an SQLite3 Database for JRuby Platform If you choose to use SQLite3 and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: @@ -700,13 +775,47 @@ Rails will now prepend "/app1" when generating links. #### Using Passenger -Passenger makes it easiy to run your application in a subdirectory. You can find -the relevant configuration in the -[passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). +Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). #### Using a Reverse Proxy -TODO +Deploying your application using a reverse proxy has definite advantages over traditional deploys. They allow you to have more control over your server by layering the components required by your application. + +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. + +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. + +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 { + listen 80; + server_name localhost; + + root /root/path/to/your_app/public; + + try_files $uri/index.html $uri.html @app; + + location @app { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://application_server; + } + + # some other configuration +} +``` + +Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information. #### Considerations when deploying to a subdirectory @@ -918,4 +1027,25 @@ ActiveRecord::ConnectionTimeoutError - could not obtain a database connection wi If you get the above error, you might want to increase the size of connection pool by incrementing the `pool` option in `database.yml` -NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections. +NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections. + + +Custom configuration +-------------------- + +You can configure your own code through the Rails configuration object with custom configuration. It works like this: + + ```ruby + config.x.payment_processing.schedule = :daily + config.x.payment_processing.retries = 3 + config.x.super_debugger = true + ``` + +These configuration points are then available through the configuration object: + + ```ruby + Rails.configuration.x.payment_processing.schedule # => :daily + Rails.configuration.x.payment_processing.retries # => 3 + Rails.configuration.x.super_debugger # => true + Rails.configuration.x.super_debugger.not_set # => nil + ``` diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 814237ba22..7381521658 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Contributing to Ruby on Rails ============================= @@ -24,9 +26,9 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge ### Creating a Bug Report -If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) +If you've found a problem in Ruby on Rails which is not a security risk, do a search on GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) -At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. +Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment. @@ -50,7 +52,7 @@ Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to -Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you +Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed. Sometimes, the line between 'bug' and 'feature' is a hard one to draw. @@ -69,89 +71,6 @@ won't be accepted." But it's the proper place to discuss new ideas. GitHub Issues are not a particularly good venue for the sometimes long and involved discussions new features require. -Setting Up a Development Environment ------------------------------------- - -To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer. - -### 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 Hard Way - -In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html). - - -Running an Application Against Your Local Branch ------------------------------------------------- - -The `--dev` flag of `rails new` generates an application that uses your local -branch: - -```bash -$ cd rails -$ bundle exec rails new ~/my-test-app --dev -``` - -The application generated in `~/my-test-app` runs against your local branch -and in particular sees any modifications upon server reboot. - - -Testing Active Record ---------------------- - -This is how you run the Active Record test suite only for SQLite3: - -```bash -$ cd activerecord -$ bundle exec rake test_sqlite3 -``` - -You can now run the tests as you did for `sqlite3`. The tasks are respectively - -```bash -test_mysql -test_mysql2 -test_postgresql -``` - -Finally, - -```bash -$ bundle exec rake test -``` - -will now run the four of them in turn. - -You can also run any single test separately: - -```bash -$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb -``` - -You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. - -### Warnings - -The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. - -As of this writing (December, 2010) they are especially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: - -```bash -$ RUBYOPT=-W0 bundle exec rake test -``` - -### Older Versions of Ruby on Rails - -If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch: - -```bash -$ git branch --track 3-0-stable origin/3-0-stable -$ git checkout 3-0-stable -``` - -TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with. Helping to Resolve Existing Issues ---------------------------------- @@ -192,16 +111,15 @@ After applying their branch, test it out! Here are some things to think about: Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: -<blockquote> -I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too. -</blockquote> +>I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too. If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. Contributing to the Rails Documentation --------------------------------------- -Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference. +Ruby on Rails has two main sets of documentation: the guides, which help you +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 it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). @@ -226,9 +144,21 @@ WARNING: Docrails has a very strict policy: no code can be touched whatsoever, n Contributing to the Rails Code ------------------------------ +### Setting Up a Development Environment + +To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer. + +#### 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 Hard Way + +In case you can't use the Rails development box, see [this other guide](development_dependencies_install.html). + ### Clone the Rails Repository -The first thing you need to do to be able to contribute code is to clone the repository: +To be able to contribute code, you need to clone the Rails repository: ```bash $ git clone git://github.com/rails/rails.git @@ -243,29 +173,31 @@ $ git checkout -b my_new_branch It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. +### Running an Application Against Your Local Branch + +In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: + +```bash +$ cd rails +$ bundle exec rails new ~/my-test-app --dev +``` + +The application generated in `~/my-test-app` runs against your local branch +and in particular sees any modifications upon server reboot. + ### Write Your Code -Now get busy and add or edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: +Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: * Get the code right. * Use Rails idioms and helpers. * Include tests that fail without your code, and pass with it. * Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution. -It is not customary in Rails to run the full test suite before pushing -changes. The railties test suite in particular takes a long time, and even -more if the source code is mounted in `/vagrant` as happens in the recommended -workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). - -As a compromise, test what your code obviously affects, and if the change is -not in railties run the whole test suite of the affected component. If all is -green that's enough to propose your contribution. We have [Travis CI](https://travis-ci.org/rails/rails) -as a safety net for catching unexpected breakages -elsewhere. -TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. +TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted (read more about [our rationales behind this decision](https://github.com/rails/rails/pull/13771#issuecomment-32746700)). -### Follow the Coding Conventions +#### Follow the Coding Conventions Rails follows a simple set of coding style conventions: @@ -275,7 +207,7 @@ Rails follows a simple set of coding style conventions: * Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`. * Prefer `&&`/`||` over `and`/`or`. * Prefer class << self over self.method for class methods. -* Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`. +* Use `my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`. * Use `a = b` and not `a=b`. * Use assert_not methods instead of refute. * Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks. @@ -283,13 +215,141 @@ Rails follows a simple set of coding style conventions: The above are guidelines - please use your best judgment in using them. +### Benchmark Your Code + +If your change has an impact on the performance of Rails, please use the +[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide +benchmark results for comparison. + +Here's an example of using benchmark-ips: + +```ruby +require 'benchmark/ips' + +Benchmark.ips do |x| + x.report('addition') { 1 + 2 } + x.report('addition with send') { 1.send(:+, 2) } +end +``` + +This will generate a report with the following information: + +``` +Calculating ------------------------------------- + addition 69114 i/100ms + addition with send 64062 i/100ms +------------------------------------------------- + addition 5307644.4 (±3.5%) i/s - 26539776 in 5.007219s + addition with send 3702897.9 (±3.5%) i/s - 18513918 in 5.006723s +``` + +Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information. + +### Running Tests + +It is not customary in Rails to run the full test suite before pushing +changes. The railties test suite in particular takes a long time, and even +more if the source code is mounted in `/vagrant` as happens in the recommended +workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). + +As a compromise, test what your code obviously affects, and if the change is +not in railties, run the whole test suite of the affected component. If all +tests are passing, that's enough to propose your contribution. We have +[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching +unexpected breakages elsewhere. + +#### Entire Rails: + +To run all the tests, do: + +```bash +$ cd rails +$ bundle exec rake test +``` + +#### For a Particular Component + +You can run tests only for a particular component (e.g. Action Pack). For example, +to run Action Mailer tests: + +```bash +$ cd actionmailer +$ bundle exec rake test +``` + +#### Running a Single Test + +You can run a single test through ruby. For instance: + +```bash +$ cd actionmailer +$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout +``` + +The `-n` option allows you to run a single method instead of the whole +file. + +#### Testing Active Record + +First, create the databases you'll need. For MySQL and PostgreSQL, +running the SQL statements `create database activerecord_unittest` and +`create database activerecord_unittest2` is sufficient. This is not +necessary for SQLite3. + +This is how you run the Active Record test suite only for SQLite3: + +```bash +$ cd activerecord +$ bundle exec rake test:sqlite3 +``` + +You can now run the tests as you did for `sqlite3`. The tasks are respectively + +```bash +test:mysql +test:mysql2 +test:postgresql +``` + +Finally, + +```bash +$ bundle exec rake test +``` + +will now run the four of them in turn. + +You can also run any single test separately: + +```bash +$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb +``` + +To run a single test against all adapters, use: + +```bash +$ bundle exec rake TEST=test/cases/associations/has_many_associations_test.rb +``` + +You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. + +### Warnings + +The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. + +If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: + +```bash +$ RUBYOPT=-W0 bundle exec rake test +``` + ### Updating the CHANGELOG The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version. You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG. -A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry: +A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry: ``` * Summary of a change that briefly describes what was changed. You can use multiple @@ -313,9 +373,9 @@ Your name can be added directly after the last word if you don't provide any cod 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 +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 @@ -342,9 +402,9 @@ it should not be necessary to visit a webpage to check the history. Description can have multiple paragraphs and you can use code examples inside, just indent it with 4 spaces: - class PostsController + class ArticlesController def index - respond_with Post.limit(10) + render json: Article.limit(10) end end @@ -466,11 +526,11 @@ the same way that you appreciate feedback on your patches. ### Iterate as Necessary -It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem. +It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into the knowledge of the community. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem. #### Squashing commits -One of the things that we may ask you to do is "squash your commits," which +One of the things that we may ask you to do is to "squash your commits", which will combine all of your commits into a single commit. We prefer pull requests that are a single commit. This makes it easier to backport changes to stable branches, squashing makes it easier to revert bad commits, and the git history @@ -506,7 +566,35 @@ $ git push origin my_pull_request -f You should be able to refresh the pull request on GitHub and see that it has been updated. -### Backporting +#### Updating pull request + +Sometimes you will be asked to make some changes to the code you have +already committed. This can include amending existing commits. In this +case Git will not allow you to push the changes as the pushed branch +and local branch do not match. Instead of opening a new pull request, +you can force push to your branch on GitHub as described earlier in +squashing commits section: + +```bash +$ git push origin my_pull_request -f +``` + +This will update the branch and pull request on GitHub with your new code. Do +note that using force push may result in commits being lost on the remote branch; use it with care. + + +### Older Versions of Ruby on Rails + +If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 4-0-stable branch: + +```bash +$ git branch --track 4-0-stable origin/4-0-stable +$ git checkout 4-0-stable +``` + +TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with. + +#### Backporting Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort. diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 7c6858fa2c..61ea0b44ef 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -40,7 +40,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <% end %> <%= author('Tore Darell', 'toretore') do %> - Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the Internet is his blog <a href="http://tore.darell.no">Sneaky Abstractions</a>. + Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. You can follow him on <a href="http://twitter.com/toretore">Twitter</a>. <% end %> <%= author('Jeff Dean', 'zilkey') do %> @@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <% end %> <%= author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails developer at <a href="http://www.37signals.com">37signals</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. + Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. <% end %> <%= author('Emilio Tagua', 'miloops') do %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 226137c89a..a788dd48ad 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Debugging Rails Applications ============================ @@ -26,17 +28,17 @@ One common task is to inspect the contents of a variable. In Rails, you can do t The `debug` helper will return a \<pre> tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view: ```html+erb -<%= debug @post %> +<%= debug @article %> <p> <b>Title:</b> - <%= @post.title %> + <%= @article.title %> </p> ``` You'll see something like this: ```yaml ---- !ruby/object:Post +--- !ruby/object Article attributes: updated_at: 2008-09-05 22:55:47 body: It's a very helpful guide for debugging your Rails app. @@ -55,10 +57,10 @@ Title: Rails debugging guide Displaying an instance variable, or any other object or method, in YAML format can be achieved this way: ```html+erb -<%= simple_format @post.to_yaml %> +<%= simple_format @article.to_yaml %> <p> <b>Title:</b> - <%= @post.title %> + <%= @article.title %> </p> ``` @@ -67,7 +69,7 @@ The `to_yaml` method converts the method to YAML format leaving it more readable As a result of this, you will have something like this in your view: ```yaml ---- !ruby/object:Post +--- !ruby/object Article attributes: updated_at: 2008-09-05 22:55:47 body: It's a very helpful guide for debugging your Rails app. @@ -88,7 +90,7 @@ Another useful method for displaying object values is `inspect`, especially when <%= [1, 2, 3, 4, 5].inspect %> <p> <b>Title:</b> - <%= @post.title %> + <%= @article.title %> </p> ``` @@ -123,7 +125,7 @@ config.logger = Logger.new(STDOUT) config.logger = Log4r::Logger.new("Application Log") ``` -TIP: By default, each log is created under `Rails.root/log/` and the log file name is `environment_name.log`. +TIP: By default, each log is created under `Rails.root/log/` and the log file is named after the environment in which the application is running. ### Log Levels @@ -138,7 +140,7 @@ Rails.logger.level = 0 # at any time This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information. -TIP: The default Rails log level is `info` in production mode and `debug` in development and test mode. +TIP: The default Rails log level is `debug` in all environments. ### Sending Messages @@ -153,18 +155,18 @@ logger.fatal "Terminating application, raised unrecoverable error!!!" Here's an example of a method instrumented with extra logging: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController # ... def create - @post = Post.new(params[:post]) - logger.debug "New post: #{@post.attributes.inspect}" - logger.debug "Post should be valid: #{@post.valid?}" - - if @post.save - flash[:notice] = 'Post was successfully created.' - logger.debug "The post was saved and now the user is going to be redirected..." - redirect_to(@post) + @article = Article.new(params[:article]) + 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) else render action: "new" end @@ -177,21 +179,21 @@ end Here's an example of the log generated when this controller action is executed: ``` -Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST] +Processing ArticlesController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST] Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4 - Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails", + Parameters: {"commit"=>"Create", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, - "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"posts"} -New post: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", + "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} -Post should be valid: true - Post Create (0.000443) INSERT INTO "posts" ("updated_at", "title", "body", "published", +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') -The post was saved and now the user is going to be redirected... -Redirected to #<Post:0x20af760> -Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts] +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] ``` 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. @@ -210,8 +212,8 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs " ``` ### Impact of Logs on Performance -Logging will always have a small impact on performance of your rails app, - particularly when logging to disk.However, there are a few subtleties: +Logging will always have a small impact on performance of your rails app, + particularly when logging to disk. However, there are a few subtleties: Using the `:debug` level will have a greater performance penalty than `:fatal`, as a far greater number of strings are being evaluated and written to the @@ -224,446 +226,576 @@ Another potential pitfall is that if you have many calls to `Logger` like this logger.debug "Person attributes hash: #{@person.attributes.inspect}" ``` -In the above example, There will be a performance impact even if the allowed -output level doesn't include debug. The reason is that Ruby has to evaluate -these strings, which includes instantiating the somewhat heavy `String` object +In the above example, There will be a performance impact even if the allowed +output level doesn't include debug. The reason is that Ruby has to evaluate +these strings, which includes instantiating the somewhat heavy `String` object and interpolating the variables, and which takes time. -Therefore, it's recommended to pass blocks to the logger methods, as these are -only evaluated if the output level is the same or included in the allowed level +Therefore, it's recommended to pass blocks to the logger methods, as these are +only evaluated if the output level is the same or included in the allowed level (i.e. lazy loading). The same code rewritten would be: ```ruby logger.debug {"Person attributes hash: #{@person.attributes.inspect}"} ``` -The contents of the block, and therefore the string interpolation, is only -evaluated if debug is enabled. This performance savings is only really +The contents of the block, and therefore the string interpolation, is only +evaluated if debug is enabled. This performance savings is only really noticeable with large amounts of logging, but it's a good practice to employ. -Debugging with the `debugger` gem +Debugging with the `byebug` gem --------------------------------- -When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion. +When your code is behaving in unexpected ways, you can try printing to logs or +the console to diagnose the problem. Unfortunately, there are times when this +sort of error tracking is not effective in finding the root cause of a problem. +When you actually need to journey into your running source code, the debugger +is your best companion. -The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written deeper into Rails code. +The debugger can also help you if you want to learn about the Rails source code +but don't know where to start. Just debug any request to your application and +use this guide to learn how to move from the code you have written deeper into +Rails code. ### Setup -You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run: +You can use the `byebug` gem to set breakpoints and step through live code in +Rails. To install it, just run: ```bash -$ gem install debugger +$ gem install byebug ``` -Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the `debugger` method. +Inside any Rails application you can then invoke the debugger by calling the +`byebug` method. Here's an example: ```ruby class PeopleController < ApplicationController def new - debugger + byebug @person = Person.new end end ``` -If you see this message in the console or logs: +### The Shell + +As soon as your application calls the `byebug` method, the debugger will be +started in a debugger shell inside the terminal window where you launched your +application server, and you will be placed at the debugger's prompt `(byebug)`. +Before the prompt, the code around the line that is about to be run will be +displayed and the current line will be marked by '=>'. Like this: ``` -***** Debugger requested, but was not available: Start server with --debugger to enable ***** +[1, 10] in /PathTo/project/app/controllers/articles_controller.rb + 3: + 4: # GET /articles + 5: # GET /articles.json + 6: def index + 7: byebug +=> 8: @articles = Article.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @articles } + +(byebug) ``` -Make sure you have started your web server with the option `--debugger`: +If you got there by a browser request, the browser tab containing the request +will be hung until the debugger has finished and the trace has finished +processing the entire request. + +For example: ```bash -$ rails server --debugger => Booting WEBrick -=> Rails 4.0.0 application starting on http://0.0.0.0:3000 -=> Debugger enabled -... -``` +=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000 +=> Run `rails server -h` for more startup options +=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option) +=> Ctrl-C to shutdown server +[2014-04-11 13:11:47] INFO WEBrick 1.3.1 +[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux] +[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000 -TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, even if it was started without `--debugger`. -### The Shell +Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200 + ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations" +Processing by ArticlesController#index as HTML -As soon as your application calls the `debugger` method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt `(rdb:n)`. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run. +[3, 12] in /PathTo/project/app/controllers/articles_controller.rb + 3: + 4: # GET /articles + 5: # GET /articles.json + 6: def index + 7: byebug +=> 8: @articles = Article.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @articles } -If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request. +(byebug) +``` -For example: +Now it's time to explore and dig into your application. A good place to start is +by asking the debugger for help. Type: `help` -```bash -@posts = Post.all -(rdb:7) ``` +(byebug) help -Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help` +byebug 2.7.0 -``` -(rdb:7) help -ruby-debug help v0.10.2 Type 'help <command-name>' for help on a specific command Available commands: -backtrace delete enable help next quit show trace -break disable eval info p reload source undisplay -catch display exit irb pp restart step up -condition down finish list ps save thread var -continue edit frame method putl set tmate where +backtrace delete enable help list pry next restart source up +break disable eval info method ps save step var +catch display exit interrupt next putl set thread +condition down finish irb p quit show trace +continue edit frame kill pp reload skip undisplay ``` -TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_ +TIP: To view the help menu for any command use `help <command-name>` at the +debugger prompt. For example: _`help list`_. You can abbreviate any debugging +command by supplying just enough letters to distinguish them from other +commands, so you can also use `l` for the `list` command, for example. -The next command to learn is one of the most useful: `list`. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use `l` for the `list` command. +To see the previous ten lines you should type `list-` (or `l-`) -This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by `=>`. - -``` -(rdb:7) list -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } ``` +(byebug) l- -If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out. +[1, 10] in /PathTo/project/app/controllers/articles_controller.rb + 1 class ArticlesController < ApplicationController + 2 before_action :set_article, only: [:show, :edit, :update, :destroy] + 3 + 4 # GET /articles + 5 # GET /articles.json + 6 def index + 7 byebug + 8 @articles = Article.find_recent + 9 + 10 respond_to do |format| ``` -(rdb:7) l -[11, 20] in /PathTo/project/app/controllers/posts_controller.rb - 11 end - 12 end - 13 - 14 # GET /posts/1 - 15 # GET /posts/1.json - 16 def show - 17 @post = Post.find(params[:id]) - 18 - 19 respond_to do |format| - 20 format.html # show.html.erb -``` -And so on until the end of the current file. When the end of file is reached, the `list` command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer. +This way you can move inside the file, being able to see the code above and over +the line where you added the `byebug` call. Finally, to see where you are in +the code again you can type `list=` -On the other hand, to see the previous ten lines you should type `list-` (or `l-`) - -``` -(rdb:7) l- -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger - 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } ``` +(byebug) list= -This way you can move inside the file, being able to see the code above and over the line you added the `debugger`. -Finally, to see where you are in the code again you can type `list=` +[3, 12] in /PathTo/project/app/controllers/articles_controller.rb + 3: + 4: # GET /articles + 5: # GET /articles.json + 6: def index + 7: byebug +=> 8: @articles = Article.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @articles } -``` -(rdb:7) list= -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } +(byebug) ``` ### The Context -When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack. - -The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. - -At any time you can call the `backtrace` command (or its alias `where`) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then `backtrace` will supply the answer. - -``` -(rdb:5) where - #0 PostsController.index - at line /PathTo/project/app/controllers/posts_controller.rb:6 - #1 Kernel.send - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...) - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617 +When you start debugging your application, you will be placed in different +contexts as you go through the different parts of the stack. + +The debugger creates a context when a stopping point or an event is reached. The +context has information about the suspended program which enables the debugger +to inspect the frame stack, evaluate variables from the perspective of the +debugged program, and contains information about the place where the debugged +program is stopped. + +At any time you can call the `backtrace` command (or its alias `where`) to print +the backtrace of the application. This can be very helpful to know how you got +where you are. If you ever wondered about how you got somewhere in your code, +then `backtrace` will supply the answer. + +``` +(byebug) where +--> #0 ArticlesController.index + at /PathTo/project/test_app/app/controllers/articles_controller.rb:8 + #1 ActionController::ImplicitRender.send_action(method#String, *args#Array) + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4 + #2 AbstractController::Base.process_action(action#NilClass, *args#Array) + at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189 + #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass) + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10 ... ``` -You move anywhere you want in this trace (thus changing the context) by using the `frame _n_` command, where _n_ is the specified frame number. +The current frame is marked with `-->`. You can move anywhere you want in this +trace (thus changing the context) by using the `frame _n_` command, where _n_ is +the specified frame number. If you do that, `byebug` will display your new +context. ``` -(rdb:5) frame 2 -#2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 +(byebug) frame 2 + +[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb + 184: # is the intended way to override action dispatching. + 185: # + 186: # Notice that the first argument is the method to be dispatched + 187: # which is *not* necessarily the same as the action name. + 188: def process_action(method_name, *args) +=> 189: send_action(method_name, *args) + 190: end + 191: + 192: # Actually call the method associated with the action. Override + 193: # this method if you wish to change how action methods are called, + +(byebug) ``` -The available variables are the same as if you were running the code line by line. After all, that's what debugging is. +The available variables are the same as if you were running the code line by +line. After all, that's what debugging is. -Moving up and down the stack frame: You can use `up [n]` (`u` for abbreviated) and `down [n]` commands in order to change the context _n_ frames up or down the stack respectively. _n_ defaults to one. Up in this case is towards higher-numbered stack frames, and down is towards lower-numbered stack frames. +You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order +to change the context _n_ frames up or down the stack respectively. _n_ defaults +to one. Up in this case is towards higher-numbered stack frames, and down is +towards lower-numbered stack frames. ### Threads -The debugger can list, stop, resume and switch between running threads by using the command `thread` (or the abbreviated `th`). This command has a handful of options: +The debugger can list, stop, resume and switch between running threads by using +the `thread` command (or the abbreviated `th`). This command has a handful of +options: * `thread` shows the current thread. -* `thread list` is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution. +* `thread list` is used to list all threads and their statuses. The plus + +character and the number indicates the current thread of execution. * `thread stop _n_` stop thread _n_. * `thread resume _n_` resumes thread _n_. * `thread switch _n_` switches the current thread context to _n_. -This command is very helpful, among other occasions, when you are debugging concurrent threads and need to verify that there are no race conditions in your code. +This command is very helpful, among other occasions, when you are debugging +concurrent threads and need to verify that there are no race conditions in your +code. ### Inspecting Variables -Any expression can be evaluated in the current context. To evaluate an expression, just type it! - -This example shows how you can print the instance_variables defined within the current context: - -``` -@posts = Post.all -(rdb:11) instance_variables -["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"] -``` - -As you may have figured out, all of the variables that you can access from a controller are displayed. This list is dynamically updated as you execute code. For example, run the next line using `next` (you'll learn more about this command later in this guide). - -``` -(rdb:11) next -Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET] - Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e - Parameters: {"action"=>"index", "controller"=>"posts"} -/PathToProject/posts_controller.rb:8 -respond_to do |format| +Any expression can be evaluated in the current context. To evaluate an +expression, just type it! + +This example shows how you can print the instance variables defined within the +current context: + +``` +[3, 12] in /PathTo/project/app/controllers/articles_controller.rb + 3: + 4: # GET /articles + 5: # GET /articles.json + 6: def index + 7: byebug +=> 8: @articles = Article.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @articles } + +(byebug) instance_variables +[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, + :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name, + :@_response_body, :@marked_for_same_origin_verification, :@_config] +``` + +As you may have figured out, all of the variables that you can access from a +controller are displayed. This list is dynamically updated as you execute code. +For example, run the next line using `next` (you'll learn more about this +command later in this guide). + +``` +(byebug) next +[5, 14] in /PathTo/project/app/controllers/articles_controller.rb + 5 # GET /articles.json + 6 def index + 7 byebug + 8 @articles = Article.find_recent + 9 +=> 10 respond_to do |format| + 11 format.html # index.html.erb + 12 format.json { render json: @articles } + 13 end + 14 end + 15 +(byebug) ``` And then ask again for the instance_variables: ``` -(rdb:11) instance_variables.include? "@posts" +(byebug) instance_variables.include? "@articles" true ``` -Now `@posts` is included in the instance variables, because the line defining it was executed. +Now `@articles` is included in the instance variables, because the line defining it +was executed. -TIP: You can also step into **irb** mode with the command `irb` (of course!). This way an irb session will be started within the context you invoked it. But be warned: this is an experimental feature. +TIP: You can also step into **irb** mode with the command `irb` (of course!). +This way an irb session will be started within the context you invoked it. But +be warned: this is an experimental feature. -The `var` method is the most convenient way to show variables and their values: +The `var` method is the most convenient way to show variables and their values. +Let's let `byebug` to help us with it. ``` -var -(rdb:1) v[ar] const <object> show constants of object -(rdb:1) v[ar] g[lobal] show global variables -(rdb:1) v[ar] i[nstance] <object> show instance variables of object -(rdb:1) v[ar] l[ocal] show local variables +(byebug) help var +v[ar] cl[ass] show class variables of self +v[ar] const <object> show constants of object +v[ar] g[lobal] show global variables +v[ar] i[nstance] <object> show instance variables of object +v[ar] l[ocal] show local variables ``` -This is a great way to inspect the values of the current context variables. For example: +This is a great way to inspect the values of the current context variables. For +example, to check that we have no local variables currently defined. ``` -(rdb:9) var local - __dbg_verbose_save => false +(byebug) var local +(byebug) ``` You can also inspect for an object method this way: ``` -(rdb:9) var instance Post.new -@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"... +(byebug) var instance Article.new +@_start_transaction_state = {} +@aggregation_cache = {} +@association_cache = {} +@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil} @attributes_cache = {} -@new_record = true +@changed_attributes = nil +... ``` -TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate Ruby expressions and display the value of variables to the console. +TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate +Ruby expressions and display the value of variables to the console. -You can use also `display` to start watching variables. This is a good way of tracking the values of a variable while the execution goes on. +You can use also `display` to start watching variables. This is a good way of +tracking the values of a variable while the execution goes on. ``` -(rdb:1) display @recent_comments -1: @recent_comments = +(byebug) display @articles +1: @articles = nil ``` -The variables inside the displaying list will be printed with their values after you move in the stack. To stop displaying a variable use `undisplay _n_` where _n_ is the variable number (1 in the last example). +The variables inside the displaying list will be printed with their values after +you move in the stack. To stop displaying a variable use `undisplay _n_` where +_n_ is the variable number (1 in the last example). ### Step by Step -Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution. +Now you should know where you are in the running trace and be able to print the +available variables. But lets continue and move on with the application +execution. -Use `step` (abbreviated `s`) to continue running your program until the next logical stopping point and return control to the debugger. +Use `step` (abbreviated `s`) to continue running your program until the next +logical stopping point and return control to the debugger. -TIP: You can also use `step+ n` and `step- n` to move forward or backward `n` steps respectively. +You may also use `next` which is similar to step, but function or method calls +that appear within the line of code are executed without stopping. -You may also use `next` which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps. +TIP: You can also use `step n` or `next n` to move forwards `n` steps at once. -The difference between `next` and `step` is that `step` stops at the next line of code executed, doing just a single step, while `next` moves to the next line without descending inside methods. +The difference between `next` and `step` is that `step` stops at the next line +of code executed, doing just a single step, while `next` moves to the next line +without descending inside methods. -For example, consider this block of code with an included `debugger` statement: +For example, consider the following situation: ```ruby -class Author < ActiveRecord::Base - has_one :editorial - has_many :comments +Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200 +Processing by ArticlesController#index as HTML - def find_recent_comments(limit = 10) - debugger - @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - end -end +[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb + 1: class Article < ActiveRecord::Base + 2: + 3: def self.find_recent(limit = 10) + 4: byebug +=> 5: where('created_at > ?', 1.week.ago).limit(limit) + 6: end + 7: + 8: end + +(byebug) ``` -TIP: You can use the debugger while using `rails console`. Just remember to `require "debugger"` before calling the `debugger` method. +If we use `next`, we want go deep inside method calls. Instead, byebug will go +to the next line within the same context. In this case, this is the last line of +the method, so `byebug` will jump to next next line of the previous frame. ``` -$ rails console -Loading development environment (Rails 4.0.0) ->> require "debugger" -=> [] ->> author = Author.first -=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10"> ->> author.find_recent_comments -/PathTo/project/app/models/author.rb:11 -) -``` +(byebug) next +Next went up a frame because previous frame finished -With the code stopped, take a look around: +[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb + 4: # GET /articles + 5: # GET /articles.json + 6: def index + 7: @articles = Article.find_recent + 8: +=> 9: respond_to do |format| + 10: format.html # index.html.erb + 11: format.json { render json: @articles } + 12: end + 13: end -``` -(rdb:1) list -[2, 9] in /PathTo/project/app/models/author.rb - 2 has_one :editorial - 3 has_many :comments - 4 - 5 def find_recent_comments(limit = 10) - 6 debugger -=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - 8 end - 9 end +(byebug) ``` -You are at the end of the line, but... was this line executed? You can inspect the instance variables. +If we use `step` in the same situation, we will literally go the next ruby +instruction to be executed. In this case, the activesupport's `week` method. ``` -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -``` +(byebug) step -`@recent_comments` hasn't been defined yet, so it's clear that this line hasn't been executed yet. Use the `next` command to move on in the code: +[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb + 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) + 51: end + 52: alias :day :days + 53: + 54: def weeks +=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) + 56: end + 57: alias :week :weeks + 58: + 59: def fortnights +(byebug) ``` -(rdb:1) next -/PathTo/project/app/models/author.rb:12 -@recent_comments -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -@comments = [] -@recent_comments = [] -``` - -Now you can see that the `@comments` relationship was loaded and @recent_comments defined because the line was executed. -If you want to go deeper into the stack trace you can move single `steps`, through your calling methods and into Rails code. This is one of the best ways to find bugs in your code, or perhaps in Ruby or Rails. +This is one of the best ways to find bugs in your code, or perhaps in Ruby on +Rails. ### Breakpoints -A breakpoint makes your application stop whenever a certain point in the program is reached. The debugger shell is invoked in that line. +A breakpoint makes your application stop whenever a certain point in the program +is reached. The debugger shell is invoked in that line. -You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually: +You can add breakpoints dynamically with the command `break` (or just `b`). +There are 3 possible ways of adding breakpoints manually: * `break line`: set breakpoint in the _line_ in the current source file. -* `break file:line [if expression]`: set breakpoint in the _line_ number inside the _file_. If an _expression_ is given it must evaluated to _true_ to fire up the debugger. -* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The _expression_ works the same way as with file:line. +* `break file:line [if expression]`: set breakpoint in the _line_ number inside +the _file_. If an _expression_ is given it must evaluated to _true_ to fire up +the debugger. +* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and +\# for class and instance method respectively) defined in _class_. The +_expression_ works the same way as with file:line. + + +For example, in the previous situation ``` -(rdb:5) break 10 -Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10 +[4, 13] in /PathTo/project/app/controllers/articles_controller.rb + 4: # GET /articles + 5: # GET /articles.json + 6: def index + 7: @articles = Article.find_recent + 8: +=> 9: respond_to do |format| + 10: format.html # index.html.erb + 11: format.json { render json: @articles } + 12: end + 13: end + +(byebug) break 11 +Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11 + ``` -Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. +Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you +supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. ``` -(rdb:5) info breakpoints +(byebug) info breakpoints Num Enb What - 1 y at filters.rb:10 +1 y at /PathTo/project/app/controllers/articles_controller.rb:11 ``` -To delete breakpoints: use the command `delete _n_` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active.. +To delete breakpoints: use the command `delete _n_` to remove the breakpoint +number _n_. If no number is specified, it deletes all breakpoints that are +currently active. ``` -(rdb:5) delete 1 -(rdb:5) info breakpoints +(byebug) delete 1 +(byebug) info breakpoints No breakpoints. ``` You can also enable or disable breakpoints: -* `enable breakpoints`: allow a list _breakpoints_ or all of them if no list is specified, to stop your program. This is the default state when you create a breakpoint. +* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is +specified, to stop your program. This is the default state when you create a +breakpoint. * `disable breakpoints`: the _breakpoints_ will have no effect on your program. ### Catching Exceptions -The command `catch exception-name` (or just `cat exception-name`) can be used to intercept an exception of type _exception-name_ when there would otherwise be is no handler for it. +The command `catch exception-name` (or just `cat exception-name`) can be used to +intercept an exception of type _exception-name_ when there would otherwise be no +handler for it. To list all active catchpoints use `catch`. ### Resuming Execution -There are two ways to resume execution of an application that is stopped in the debugger: - -* `continue` [line-specification] \(or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached. -* `finish` [frame-number] \(or `fin`): execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given it will run until the specified frame returns. +There are two ways to resume execution of an application that is stopped in the +debugger: + +* `continue` [line-specification] \(or `c`): resume program execution, at the +address where your script last stopped; any breakpoints set at that address are +bypassed. The optional argument line-specification allows you to specify a line +number to set a one-time breakpoint which is deleted when that breakpoint is +reached. +* `finish` [frame-number] \(or `fin`): execute until the selected stack frame +returns. If no frame number is given, the application will run until the +currently selected frame returns. The currently selected frame starts out the +most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been +performed. If a frame number is given it will run until the specified frame +returns. ### Editing Two commands allow you to open code from the debugger into an editor: -* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR environment variable. A specific _line_ can also be given. -* `tmate _n_` (abbreviated `tm`): open the current file in TextMate. It uses n-th frame if _n_ is specified. +* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR +environment variable. A specific _line_ can also be given. ### Quitting -To exit the debugger, use the `quit` command (abbreviated `q`), or its alias `exit`. +To exit the debugger, use the `quit` command (abbreviated `q`), or its alias +`exit`. -A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again. +A simple quit tries to terminate all threads in effect. Therefore your server +will be stopped and you will have to start it again. ### Settings -The `debugger` gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options: - -* `set reload`: Reload source code when changed. -* `set autolist`: Execute `list` command on every breakpoint. -* `set listsize _n_`: Set number of source lines to list by default to _n_. -* `set forcestep`: Make sure the `next` and `step` commands always move to a new line +`byebug` has a few available options to tweak its behaviour: -You can see the full list by using `help set`. Use `help set _subcommand_` to learn about a particular `set` command. +* `set autoreload`: Reload source code when changed (default: true). +* `set autolist`: Execute `list` command on every breakpoint (default: true). +* `set listsize _n_`: Set number of source lines to list by default to _n_ +(default: 10) +* `set forcestep`: Make sure the `next` and `step` commands always move to a new +line. -TIP: You can save these settings in an `.rdebugrc` file in your home directory. The debugger reads these global settings when it starts. +You can see the full list by using `help set`. Use `help set _subcommand_` to +learn about a particular `set` command. -Here's a good start for an `.rdebugrc`: +TIP: You can save these settings in an `.byebugrc` file in your home directory. +The debugger reads these global settings when it starts. For example: ```bash -set autolist set forcestep set listsize 25 ``` @@ -671,35 +803,59 @@ set listsize 25 Debugging Memory Leaks ---------------------- -A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level. +A Ruby application (on Rails or not), can leak memory - either in the Ruby code +or at the C code level. -In this section, you will learn how to find and fix such leaks by using tool such as Valgrind. +In this section, you will learn how to find and fix such leaks by using tool +such as Valgrind. ### Valgrind -[Valgrind](http://valgrind.org/) is a Linux-only application for detecting C-based memory leaks and race conditions. +[Valgrind](http://valgrind.org/) is a Linux-only application for detecting +C-based memory leaks and race conditions. -There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C extension in the interpreter calls `malloc()` but doesn't properly call `free()`, this memory won't be available until the app terminates. +There are Valgrind tools that can automatically detect many memory management +and threading bugs, and profile your programs in detail. For example, if a C +extension in the interpreter calls `malloc()` but doesn't properly call +`free()`, this memory won't be available until the app terminates. -For further information on how to install Valgrind and use with Ruby, refer to [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) by Evan Weaver. +For further information on how to install Valgrind and use with Ruby, refer to +[Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) +by Evan Weaver. Plugins for Debugging --------------------- -There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging: - -* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate. -* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs. -* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed. -* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application. -* [Better Errors](https://github.com/charliesome/better_errors) Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection. -* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser - in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. +There are some Rails plugins to help you to find errors and debug your +application. Here is a list of useful plugins for debugging: + +* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has +footnotes that give request information and link back to your source via +TextMate. +* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query +origin tracing to your logs. +* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin +not only runs "EXPLAIN" before each of your select queries in development, but +provides a small DIV in the rendered output of each page with the summary of +warnings for each query that it analyzed. +* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) +Provides a mailer object and a default set of templates for sending email +notifications when errors occur in a Rails application. +* [Better Errors](https://github.com/charliesome/better_errors) Replaces the +standard Rails error page with a new one containing more contextual information, +like source code and variable inspection. +* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails +development that will end your tailing of development.log. Have all information +about your Rails app requests in the browser - in the Developer Tools panel. +Provides insight to db/rendering/total times, parameter list, rendered views and +more. References ---------- * [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html) * [debugger Homepage](https://github.com/cldwalker/debugger) +* [byebug Homepage](https://github.com/deivid-rodriguez/byebug) * [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/) * [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised) * [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace) diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 4ee43b6a97..989b29956c 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Development Dependencies Install ================================ @@ -45,36 +47,14 @@ $ 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 libxml2 and libxslt together with their development files for Nokogiri. In Ubuntu that's - -```bash -$ sudo apt-get install libxml2 libxml2-dev libxslt1-dev -``` - -If you are on Fedora or CentOS, you can run - -```bash -$ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel -``` - -If you are running Arch Linux, you're done with: +Install first SQLite3 and its development files for the `sqlite3` gem. Mac OS X +users are done with: ```bash -$ sudo pacman -S libxml2 libxslt +$ brew install sqlite3 ``` -On FreeBSD, you just have to run: - -```bash -# pkg_add -r libxml2 libxslt -``` - -Alternatively, you can install the `textproc/libxml2` and `textproc/libxslt` -ports. - -If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . - -Also, SQLite3 and its development files for the `sqlite3-ruby` gem - in Ubuntu you're done with just +In Ubuntu you're done with just: ```bash $ sudo apt-get install sqlite3 libsqlite3-dev @@ -95,12 +75,12 @@ $ sudo pacman -S sqlite For FreeBSD users, you're done with: ```bash -# pkg_add -r sqlite3 +# pkg install sqlite3 ``` Or compile the `databases/sqlite3` port. -Get a recent version of [Bundler](http://gembundler.com/) +Get a recent version of [Bundler](http://bundler.io/) ```bash $ gem install bundler @@ -117,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 to install memcached on OSX: +You can use [Homebrew](http://brew.sh/) to install memcached on OS X: ```bash $ brew install memcached @@ -135,6 +115,20 @@ Or use yum on Fedora or CentOS: $ sudo yum install memcached ``` +If you are running on Arch Linux: + +```bash +$ sudo pacman -S memcached +``` + +For FreeBSD users, you're done with: + +```bash +# pkg install memcached +``` + +Alternatively, you can compile the `databases/memcached` port. + With the dependencies now installed, you can run the test suite with: ```bash @@ -181,7 +175,19 @@ The Active Record test suite requires a custom config file: `activerecord/test/c #### MySQL and PostgreSQL -To be able to run the suite for MySQL and PostgreSQL we need their gems. Install first the servers, their client libraries, and their development files. In Ubuntu just run +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: + +```bash +$ brew install mysql +$ brew install postgresql +``` + +Follow the instructions given by Homebrew to start these. + +In Ubuntu just run: ```bash $ sudo apt-get install mysql-server libmysqlclient15-dev @@ -206,8 +212,8 @@ $ sudo pacman -S postgresql postgresql-libs FreeBSD users will have to run the following: ```bash -# pkg_add -r mysql56-client mysql56-server -# pkg_add -r postgresql92-client postgresql92-server +# pkg install mysql56-client mysql56-server +# pkg install postgresql93-client postgresql93-server ``` Or install them through ports (they are located under the `databases` folder). @@ -241,20 +247,27 @@ and create the test databases: ```bash $ cd activerecord -$ bundle exec rake mysql:build_databases +$ bundle exec rake db:mysql:build ``` -PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account +PostgreSQL's authentication works differently. To setup the development environment +with your development account, on Linux or BSD, you just have to run: ```bash $ sudo -u postgres createuser --superuser $USER ``` -and then create the test databases with +and for OS X: + +```bash +$ createuser --superuser $USER +``` + +Then you need to create the test databases with ```bash $ cd activerecord -$ bundle exec rake postgresql:build_databases +$ bundle exec rake db:postgresql:build ``` It is possible to build databases for both PostgreSQL and MySQL with diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index ae47744e31..67032a31f5 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -13,8 +13,8 @@ url: active_record_basics.html description: This guide will get you started with models, persistence to database and the Active Record pattern and library. - - name: Rails Database Migrations - url: migrations.html + name: Active Record Migrations + url: active_record_migrations.html description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. - name: Active Record Validations @@ -32,6 +32,11 @@ name: Active Record Query Interface url: active_record_querying.html description: This guide covers the database query interface provided by Active Record. + - + name: Active Model basics + url: active_model_basics.html + description: This guide covers the use of model classes without Active Record. + work_in_progress: true - name: Views documents: @@ -75,10 +80,14 @@ url: action_mailer_basics.html description: This guide describes how to use Action Mailer to send and receive emails. - + name: Active Job Basics + url: active_job_basics.html + description: This guide provides you with all you need to get started in creating, enqueueing and executing background jobs. + - name: Testing Rails Applications - url: testing.html work_in_progress: true - description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. + 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 the testing APIs. Enjoy. - name: Securing Rails Applications url: security.html @@ -96,11 +105,6 @@ url: command_line.html description: This guide covers the command line tools and rake tasks provided by Rails. - - name: Caching with Rails - work_in_progress: true - url: caching_with_rails.html - description: Various caching techniques provided by Rails. - - name: Asset Pipeline url: asset_pipeline.html description: This guide documents the asset pipeline. @@ -109,15 +113,25 @@ url: working_with_javascript_in_rails.html description: This guide covers the built-in Ajax/JavaScript functionality of Rails. - - name: Getting Started with Engines - url: engines.html - description: This guide explains how to write a mountable engine. - work_in_progress: true - - name: The Rails Initialization Process work_in_progress: true url: initialization.html - description: This guide explains the internals of the Rails initialization process as of Rails 3.1 + description: This guide explains the internals of the Rails initialization process as of Rails 4 + - + name: Autoloading and Reloading Constants + url: autoloading_and_reloading_constants.html + description: This guide documents how autoloading and reloading constants work. + - + name: Active Support Instrumentation + work_in_progress: true + 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: Extending Rails documents: @@ -134,6 +148,11 @@ name: Creating and Customizing Rails Generators 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). + - + name: Getting Started with Engines + url: engines.html + description: This guide explains how to write a mountable engine. + work_in_progress: true - name: Contributing to Ruby on Rails documents: @@ -162,12 +181,14 @@ - name: Upgrading Ruby on Rails url: upgrading_ruby_on_rails.html - work_in_progress: true description: This guide helps in upgrading applications to latest Ruby on Rails versions. - + name: Ruby on Rails 4.2 Release Notes + url: 4_2_release_notes.html + description: Release notes for Rails 4.2. + - name: Ruby on Rails 4.1 Release Notes url: 4_1_release_notes.html - work_in_progress: true description: Release notes for Rails 4.1. - name: Ruby on Rails 4.0 Release Notes diff --git a/guides/source/engines.md b/guides/source/engines.md index bbd63bb892..6eb558885f 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Getting Started with Engines ============================ @@ -31,27 +33,28 @@ Engines are also closely related to plugins. The two share a common `lib` directory structure, and are both generated using the `rails plugin new` generator. The difference is that an engine is considered a "full plugin" by Rails (as indicated by the `--full` option that's passed to the generator -command). This guide will refer to them simply as "engines" throughout. An -engine **can** be a plugin, and a plugin **can** be an engine. +command). We'll actually be using the `--mountable` option here, which includes +all the features of `--full`, and then some. This guide will refer to these +"full plugins" simply as "engines" throughout. An engine **can** be a plugin, +and a plugin **can** be an engine. -The engine that will be created in this guide will be called "blorgh". The +The engine that will be created in this guide will be called "blorgh". This engine will provide blogging functionality to its host applications, allowing -for new posts and comments to be created. At the beginning of this guide, you +for new articles and comments to be created. At the beginning of this guide, you will be working solely within the engine itself, but in later sections you'll see how to hook it into an application. Engines can also be isolated from their host applications. This means that an application is able to have a path provided by a routing helper such as -`posts_path` and use an engine also that provides a path also called -`posts_path`, and the two would not clash. Along with this, controllers, models +`articles_path` and use an engine also that provides a path also called +`articles_path`, and the two would not clash. Along with this, controllers, models and table names are also namespaced. You'll see how to do this later in this guide. It's important to keep in mind at all times that the application should **always** take precedence over its engines. An application is the object that -has final say in what goes on in the universe (with the universe being the -application's environment) where the engine should only be enhancing it, rather -than changing it drastically. +has final say in what goes on in its environment. The engine should +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 @@ -82,8 +85,11 @@ The full list of options for the plugin generator may be seen by typing: $ rails plugin --help ``` -The `--full` option tells the generator that you want to create an engine, -including a skeleton structure that provides the following: +The `--mountable` option tells the generator that you want to create a +"mountable" and namespace-isolated engine. This generator will provide the same +skeleton structure as would the `--full` option. The `--full` option tells the +generator that you want to create an engine, including a skeleton structure +that provides the following: * An `app` directory tree * A `config/routes.rb` file: @@ -94,7 +100,7 @@ including a skeleton structure that provides the following: ``` * A file at `lib/blorgh/engine.rb`, which is identical in function to a - * standard Rails application's `config/application.rb` file: + standard Rails application's `config/application.rb` file: ```ruby module Blorgh @@ -103,9 +109,7 @@ including a skeleton structure that provides the following: end ``` -The `--mountable` option tells the generator that you want to create a -"mountable" and namespace-isolated engine. This generator will provide the same -skeleton structure as would the `--full` option, and will add: +The `--mountable` option will add to the `--full` option: * Asset manifest files (`application.js` and `application.css`) * A namespaced `ApplicationController` stub @@ -134,7 +138,7 @@ following to the dummy application's routes file at `test/dummy/config/routes.rb`: ```ruby -mount Blorgh::Engine, at: "blorgh" +mount Blorgh::Engine => "/blorgh" ``` ### Inside an Engine @@ -171,7 +175,7 @@ Within `lib/blorgh/engine.rb` is the base class for the engine: ```ruby module Blorgh - class Engine < Rails::Engine + class Engine < ::Rails::Engine isolate_namespace Blorgh end end @@ -197,12 +201,12 @@ within the `Engine` class definition. Without it, classes generated in an engine **may** conflict with an application. What this isolation of the namespace means is that a model generated by a call -to `rails g model`, such as `rails g model post`, won't be called `Post`, but -instead be namespaced and called `Blorgh::Post`. In addition, the table for the -model is namespaced, becoming `blorgh_posts`, rather than simply `posts`. -Similar to the model namespacing, a controller called `PostsController` becomes -`Blorgh::PostsController` and the views for that controller will not be at -`app/views/posts`, but `app/views/blorgh/posts` instead. Mailers are namespaced +to `bin/rails g model`, such as `bin/rails g model article`, won't be called `Article`, but +instead be namespaced and called `Blorgh::Article`. In addition, the table for the +model is namespaced, becoming `blorgh_articles`, rather than simply `articles`. +Similar to the model namespacing, a controller called `ArticlesController` becomes +`Blorgh::ArticlesController` and the views for that controller will not be at +`app/views/articles`, but `app/views/blorgh/articles` instead. Mailers are namespaced as well. Finally, routes will also be isolated within the engine. This is one of the most @@ -253,7 +257,7 @@ This means that you will be able to generate new controllers and models for this engine very easily by running commands like this: ```bash -rails g model +$ bin/rails g model ``` Keep in mind, of course, that anything generated with these commands inside of @@ -283,74 +287,72 @@ created in the `test` directory as well. For example, you may wish to create a Providing engine functionality ------------------------------ -The engine that this guide covers provides posting and commenting functionality -and follows a similar thread to the [Getting Started +The engine that this guide covers provides submitting articles and commenting +functionality and follows a similar thread to the [Getting Started Guide](getting_started.html), with some new twists. -### Generating a Post Resource +### Generating an Article Resource -The first thing to generate for a blog engine is the `Post` model and related +The first thing to generate for a blog engine is the `Article` model and related controller. To quickly generate this, you can use the Rails scaffold generator. ```bash -$ rails generate scaffold post title:string text:text +$ bin/rails generate scaffold article title:string text:text ``` This command will output this information: ``` invoke active_record -create db/migrate/[timestamp]_create_blorgh_posts.rb -create app/models/blorgh/post.rb +create db/migrate/[timestamp]_create_blorgh_articles.rb +create app/models/blorgh/article.rb invoke test_unit -create test/models/blorgh/post_test.rb -create test/fixtures/blorgh/posts.yml +create test/models/blorgh/article_test.rb +create test/fixtures/blorgh/articles.yml invoke resource_route - route resources :posts + route resources :articles invoke scaffold_controller -create app/controllers/blorgh/posts_controller.rb +create app/controllers/blorgh/articles_controller.rb invoke erb -create app/views/blorgh/posts -create app/views/blorgh/posts/index.html.erb -create app/views/blorgh/posts/edit.html.erb -create app/views/blorgh/posts/show.html.erb -create app/views/blorgh/posts/new.html.erb -create app/views/blorgh/posts/_form.html.erb +create app/views/blorgh/articles +create app/views/blorgh/articles/index.html.erb +create app/views/blorgh/articles/edit.html.erb +create app/views/blorgh/articles/show.html.erb +create app/views/blorgh/articles/new.html.erb +create app/views/blorgh/articles/_form.html.erb invoke test_unit -create test/controllers/blorgh/posts_controller_test.rb +create test/controllers/blorgh/articles_controller_test.rb invoke helper -create app/helpers/blorgh/posts_helper.rb -invoke test_unit -create test/helpers/blorgh/posts_helper_test.rb +create app/helpers/blorgh/articles_helper.rb invoke assets invoke js -create app/assets/javascripts/blorgh/posts.js +create app/assets/javascripts/blorgh/articles.js invoke css -create app/assets/stylesheets/blorgh/posts.css +create app/assets/stylesheets/blorgh/articles.css invoke css create app/assets/stylesheets/scaffold.css ``` The first thing that the scaffold generator does is invoke the `active_record` generator, which generates a migration and a model for the resource. Note here, -however, that the migration is called `create_blorgh_posts` rather than the -usual `create_posts`. This is due to the `isolate_namespace` method called in +however, that the migration is called `create_blorgh_articles` rather than the +usual `create_articles`. This is due to the `isolate_namespace` method called in the `Blorgh::Engine` class's definition. The model here is also namespaced, -being placed at `app/models/blorgh/post.rb` rather than `app/models/post.rb` due +being placed at `app/models/blorgh/article.rb` rather than `app/models/article.rb` due to the `isolate_namespace` call within the `Engine` class. Next, the `test_unit` generator is invoked for this model, generating a model -test at `test/models/blorgh/post_test.rb` (rather than -`test/models/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml` -(rather than `test/fixtures/posts.yml`). +test at `test/models/blorgh/article_test.rb` (rather than +`test/models/article_test.rb`) and a fixture at `test/fixtures/blorgh/articles.yml` +(rather than `test/fixtures/articles.yml`). After that, a line for the resource is inserted into the `config/routes.rb` file -for the engine. This line is simply `resources :posts`, turning the +for the engine. This line is simply `resources :articles`, turning the `config/routes.rb` file for the engine into this: ```ruby Blorgh::Engine.routes.draw do - resources :posts + resources :articles end ``` @@ -362,18 +364,18 @@ be isolated from those routes that are within the application. The [Routes](#routes) section of this guide describes it in detail. Next, the `scaffold_controller` generator is invoked, generating a controller -called `Blorgh::PostsController` (at -`app/controllers/blorgh/posts_controller.rb`) and its related views at -`app/views/blorgh/posts`. This generator also generates a test for the -controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper -(`app/helpers/blorgh/posts_controller.rb`). +called `Blorgh::ArticlesController` (at +`app/controllers/blorgh/articles_controller.rb`) and its related views at +`app/views/blorgh/articles`. This generator also generates a test for the +controller (`test/controllers/blorgh/articles_controller_test.rb`) and a helper +(`app/helpers/blorgh/articles_controller.rb`). Everything this generator has created is neatly namespaced. The controller's class is defined within the `Blorgh` module: ```ruby module Blorgh - class PostsController < ApplicationController + class ArticlesController < ApplicationController ... end end @@ -382,22 +384,22 @@ end NOTE: The `ApplicationController` class being inherited from here is the `Blorgh::ApplicationController`, not an application's `ApplicationController`. -The helper inside `app/helpers/blorgh/posts_helper.rb` is also namespaced: +The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced: ```ruby module Blorgh - module PostsHelper + module ArticlesHelper ... end end ``` This helps prevent conflicts with any other engine or application that may have -a post resource as well. +an article resource as well. Finally, the assets for this resource are generated in two files: -`app/assets/javascripts/blorgh/posts.js` and -`app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little +`app/assets/javascripts/blorgh/articles.js` and +`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little later. By default, the scaffold styling is not applied to the engine because the @@ -412,46 +414,46 @@ tag of this layout: You can see what the engine has so far by running `rake db:migrate` at the root of our engine to run the migration generated by the scaffold generator, and then running `rails server` in `test/dummy`. When you open -`http://localhost:3000/blorgh/posts` you will see the default scaffold that has +`http://localhost:3000/blorgh/articles` you will see the default scaffold that has been generated. Click around! You've just generated your first engine's first functions. If you'd rather play around in the console, `rails console` will also work just -like a Rails application. Remember: the `Post` model is namespaced, so to -reference it you must call it as `Blorgh::Post`. +like a Rails application. Remember: the `Article` model is namespaced, so to +reference it you must call it as `Blorgh::Article`. ```ruby ->> Blorgh::Post.find(1) -=> #<Blorgh::Post id: 1 ...> +>> Blorgh::Article.find(1) +=> #<Blorgh::Article id: 1 ...> ``` -One final thing is that the `posts` resource for this engine should be the root +One final thing is that the `articles` resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is -mounted, they should be shown a list of posts. This can be made to happen if +mounted, they should be shown a list of articles. This can be made to happen if this line is inserted into the `config/routes.rb` file inside the engine: ```ruby -root to: "posts#index" +root to: "articles#index" ``` -Now people will only need to go to the root of the engine to see all the posts, -rather than visiting `/posts`. This means that instead of -`http://localhost:3000/blorgh/posts`, you only need to go to +Now people will only need to go to the root of the engine to see all the articles, +rather than visiting `/articles`. This means that instead of +`http://localhost:3000/blorgh/articles`, you only need to go to `http://localhost:3000/blorgh` now. ### Generating a Comments Resource -Now that the engine can create new blog posts, it only makes sense to add +Now that the engine can create new articles, it only makes sense to add commenting functionality as well. To do this, you'll need to generate a comment -model, a comment controller and then modify the posts scaffold to display +model, a comment controller and then modify the articles scaffold to display comments and allow people to create new ones. From the application root, run the model generator. Tell it to generate a -`Comment` model, with the related table having two columns: a `post_id` integer +`Comment` model, with the related table having two columns: a `article_id` integer and `text` text column. ```bash -$ rails generate model Comment post_id:integer text:text +$ bin/rails generate model Comment article_id:integer text:text ``` This will output the following: @@ -474,17 +476,17 @@ table: $ rake db:migrate ``` -To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and +To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and add this line before the "Edit" link: ```html+erb <h3>Comments</h3> -<%= render @post.comments %> +<%= render @article.comments %> ``` This line will require there to be a `has_many` association for comments defined -on the `Blorgh::Post` model, which there isn't right now. To define one, open -`app/models/blorgh/post.rb` and add this line into the model: +on the `Blorgh::Article` model, which there isn't right now. To define one, open +`app/models/blorgh/article.rb` and add this line into the model: ```ruby has_many :comments @@ -494,7 +496,7 @@ Turning the model into this: ```ruby module Blorgh - class Post < ActiveRecord::Base + class Article < ActiveRecord::Base has_many :comments end end @@ -505,9 +507,9 @@ NOTE: Because the `has_many` is defined inside a class that is inside the model for these objects, so there's no need to specify that using the `:class_name` option here. -Next, there needs to be a form so that comments can be created on a post. To add -this, put this line underneath the call to `render @post.comments` in -`app/views/blorgh/posts/show.html.erb`: +Next, there needs to be a form so that comments can be created on an article. To +add this, put this line underneath the call to `render @article.comments` in +`app/views/blorgh/articles/show.html.erb`: ```erb <%= render "blorgh/comments/form" %> @@ -519,7 +521,7 @@ directory at `app/views/blorgh/comments` and in it a new file called ```html+erb <h3>New comment</h3> -<%= form_for [@post, @post.comments.build] do |f| %> +<%= form_for [@article, @article.comments.build] do |f| %> <p> <%= f.label :text %><br> <%= f.text_area :text %> @@ -529,12 +531,12 @@ directory at `app/views/blorgh/comments` and in it a new file called ``` When this form is submitted, it is going to attempt to perform a `POST` request -to a route of `/posts/:post_id/comments` within the engine. This route doesn't -exist at the moment, but can be created by changing the `resources :posts` line +to a route of `/articles/:article_id/comments` within the engine. This route doesn't +exist at the moment, but can be created by changing the `resources :articles` line inside `config/routes.rb` into these lines: ```ruby -resources :posts do +resources :articles do resources :comments end ``` @@ -545,7 +547,7 @@ The route now exists, but the controller that this route goes to does not. To create it, run this command from the application root: ```bash -$ rails g controller comments +$ bin/rails g controller comments ``` This will generate the following things: @@ -558,8 +560,6 @@ invoke test_unit create test/controllers/blorgh/comments_controller_test.rb invoke helper create app/helpers/blorgh/comments_helper.rb -invoke test_unit -create test/helpers/blorgh/comments_helper_test.rb invoke assets invoke js create app/assets/javascripts/blorgh/comments.js @@ -567,17 +567,17 @@ invoke css create app/assets/stylesheets/blorgh/comments.css ``` -The form will be making a `POST` request to `/posts/:post_id/comments`, which +The form will be making a `POST` request to `/articles/:article_id/comments`, which will correspond with the `create` action in `Blorgh::CommentsController`. This action needs to be created, which can be done by putting the following lines inside the class definition in `app/controllers/blorgh/comments_controller.rb`: ```ruby def create - @post = Post.find(params[:post_id]) - @comment = @post.comments.create(comment_params) + @article = Article.find(params[:article_id]) + @comment = @article.comments.create(comment_params) flash[:notice] = "Comment has been created!" - redirect_to posts_path + redirect_to articles_path end private @@ -590,11 +590,11 @@ This is the final step required to get the new comment form working. Displaying the comments, however, is not quite right yet. If you were to create a comment right now, you would see this error: -``` +``` Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in: * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" * -"/Users/ryan/Sites/side_projects/blorgh/app/views" +"/Users/ryan/Sites/side_projects/blorgh/app/views" ``` The engine is unable to find the partial required for rendering the comments. @@ -612,7 +612,7 @@ line inside it: ``` The `comment_counter` local variable is given to us by the `<%= render -@post.comments %>` call, which will define it automatically and increment the +@article.comments %>` call, which will define it automatically and increment the counter as it iterates through each comment. It's used in this example to display a small number next to each comment when it's created. @@ -625,7 +625,7 @@ Hooking Into an Application Using an engine within an application is very easy. This section covers how to mount the engine into an application and the initial setup required, as well as linking the engine to a `User` class provided by the application to provide -ownership for posts and comments within the engine. +ownership for articles and comments within the engine. ### Mounting the Engine @@ -676,7 +676,7 @@ pre-defined path which may be customizable. ### Engine setup -The engine contains migrations for the `blorgh_posts` and `blorgh_comments` +The engine contains migrations for the `blorgh_articles` and `blorgh_comments` table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command: @@ -698,7 +698,7 @@ haven't been copied over already. The first run for this command will output something such as this: ```bash -Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh +Copied migration [timestamp_1]_create_blorgh_articles.rb from blorgh Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh ``` @@ -709,7 +709,7 @@ migrations in the application. To run these migrations within the context of the application, simply run `rake db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the -posts will be empty. This is because the table created inside the application is +articles will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the newly mounted engine. You'll find that it's the same as when it was only an engine. @@ -734,17 +734,19 @@ rake db:migrate SCOPE=blorgh VERSION=0 When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of -the application. In the case of the `blorgh` engine, making posts and comments +the application. In the case of the `blorgh` engine, making articles and comments have authors would make a lot of sense. A typical application might have a `User` class that would be used to represent -authors for a post or a comment. But there could be a case where the application -calls this class something different, such as `Person`. For this reason, the -engine should not hardcode associations specifically for a `User` class. +authors for an article or a comment. But there could be a case where the +application calls this class something different, such as `Person`. For this +reason, the engine should not hardcode associations specifically for a `User` +class. To keep it simple in this case, the application will have a class called `User` -that represents the users of the application. It can be generated using this -command inside the application: +that represents the users of the application (we'll get into making this +configurable further on). It can be generated using this command inside the +application: ```bash rails g model user name:string @@ -753,14 +755,14 @@ rails g model user name:string The `rake db:migrate` command needs to be run here to ensure that our application has the `users` table for future use. -Also, to keep it simple, the posts form will have a new text field called +Also, to keep it simple, the articles form will have a new text field called `author_name`, where users can elect to put their name. The engine will then take this name and either create a new `User` object from it, or find one that -already has that name. The engine will then associate the post with the found or +already has that name. The engine will then associate the article with the found or created `User` object. First, the `author_name` text field needs to be added to the -`app/views/blorgh/posts/_form.html.erb` partial inside the engine. This can be +`app/views/blorgh/articles/_form.html.erb` partial inside the engine. This can be added above the `title` field with this code: ```html+erb @@ -770,23 +772,23 @@ added above the `title` field with this code: </div> ``` -Next, we need to update our `Blorgh::PostController#post_params` method to +Next, we need to update our `Blorgh::ArticleController#article_params` method to permit the new form parameter: ```ruby -def post_params - params.require(:post).permit(:title, :text, :author_name) +def article_params + params.require(:article).permit(:title, :text, :author_name) end ``` -The `Blorgh::Post` model should then have some code to convert the `author_name` -field into an actual `User` object and associate it as that post's `author` -before the post is saved. It will also need to have an `attr_accessor` set up +The `Blorgh::Article` model should then have some code to convert the `author_name` +field into an actual `User` object and associate it as that article's `author` +before the article is saved. It will also need to have an `attr_accessor` set up for this field, so that the setter and getter methods are defined for it. To do all this, you'll need to add the `attr_accessor` for `author_name`, the association for the author and the `before_save` call into -`app/models/blorgh/post.rb`. The `author` association will be hard-coded to the +`app/models/blorgh/article.rb`. The `author` association will be hard-coded to the `User` class for the time being. ```ruby @@ -803,14 +805,14 @@ private By representing the `author` association's object with the `User` class, a link is established between the engine and the application. There needs to be a way -of associating the records in the `blorgh_posts` table with the records in the +of associating the records in the `blorgh_articles` table with the records in the `users` table. Because the association is called `author`, there should be an -`author_id` column added to the `blorgh_posts` table. +`author_id` column added to the `blorgh_articles` table. To generate this new column, run this command within the engine: ```bash -$ rails g migration add_author_id_to_blorgh_posts author_id:integer +$ bin/rails g migration add_author_id_to_blorgh_articles author_id:integer ``` NOTE: Due to the migration's name and the column specification after it, Rails @@ -828,12 +830,12 @@ $ rake blorgh:install:migrations Notice that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run. -``` -NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been +``` +NOTE Migration [timestamp]_create_blorgh_articles.rb from blorgh has been skipped. Migration with the same name already exists. NOTE Migration [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration with the same name already exists. Copied migration -[timestamp]_add_author_id_to_blorgh_posts.rb from blorgh +[timestamp]_add_author_id_to_blorgh_articles.rb from blorgh ``` Run the migration using: @@ -843,20 +845,20 @@ $ rake db:migrate ``` Now with all the pieces in place, an action will take place that will associate -an author - represented by a record in the `users` table - with a post, -represented by the `blorgh_posts` table from the engine. +an author - represented by a record in the `users` table - with an article, +represented by the `blorgh_articles` table from the engine. -Finally, the author's name should be displayed on the post's page. Add this code -above the "Title" output inside `app/views/blorgh/posts/show.html.erb`: +Finally, the author's name should be displayed on the article's page. Add this code +above the "Title" output inside `app/views/blorgh/articles/show.html.erb`: ```html+erb <p> <b>Author:</b> - <%= @post.author %> + <%= @article.author %> </p> ``` -By outputting `@post.author` using the `<%=` tag, the `to_s` method will be +By outputting `@article.author` using the `<%=` tag, the `to_s` method will be called on the object. By default, this will look quite ugly: ``` @@ -925,15 +927,15 @@ This method works like its brothers, `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`. -The next step is to switch the `Blorgh::Post` model over to this new setting. +The next step is to switch the `Blorgh::Article` model over to this new setting. Change the `belongs_to` association inside this model -(`app/models/blorgh/post.rb`) to this: +(`app/models/blorgh/article.rb`) to this: ```ruby belongs_to :author, class_name: Blorgh.author_class ``` -The `set_author` method in the `Blorgh::Post` model should also use this class: +The `set_author` method in the `Blorgh::Article` model should also use this class: ```ruby self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name) @@ -960,7 +962,7 @@ Resulting in something a little shorter, and more implicit in its behavior. The `author_class` method should always return a `Class` object. Since we changed the `author_class` method to return a `Class` instead of a -`String`, we must also modify our `belongs_to` definition in the `Blorgh::Post` +`String`, we must also modify our `belongs_to` definition in the `Blorgh::Article` model: ```ruby @@ -985,14 +987,14 @@ 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 used and then converted to a class using `constantize` in the engine later on. -Go ahead and try to create a new post. You will see that it works exactly in the +Go ahead and try to create a new article. You will see that it works exactly in the same way as before, except this time the engine is using the configuration setting in `config/initializers/blorgh.rb` to learn what the class is. There are now no strict dependencies on what the class is, only what the API for the class must be. The engine simply requires this class to define a `find_or_create_by` method which returns an object of that class, to be -associated with a post when it's created. This object, of course, should have +associated with an article when it's created. This object, of course, should have some sort of identifier by which it can be referenced. #### General Engine Configuration @@ -1036,22 +1038,43 @@ functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this: ```ruby -get :index +module Blorgh + class FooControllerTest < ActionController::TestCase + def test_index + get :index + ... + end + end +end ``` It may not function correctly. This is because the application doesn't know how to route these requests to the engine unless you explicitly tell it **how**. To -do this, you must also pass the `:use_route` option as a parameter on these -requests: +do this, you must set the `@routes` instance variable to the engine's route set +in your setup code: ```ruby -get :index, use_route: :blorgh +module Blorgh + class FooControllerTest < ActionController::TestCase + setup do + @routes = Engine.routes + end + + def test_index + get :index + ... + end + end +end ``` This tells the application that you still want to perform a `GET` request to the `index` action of this controller, but you want to use the engine's route to get there, rather than the application's one. +This also ensures that the engine's URL helpers will work as expected in your +tests. + Improving engine functionality ------------------------------ @@ -1097,12 +1120,12 @@ that isn't referenced by your main application. #### Implementing Decorator Pattern Using Class#class_eval -**Adding** `Post#time_since_created`: +**Adding** `Article#time_since_created`: ```ruby -# MyApp/app/decorators/models/blorgh/post_decorator.rb +# MyApp/app/decorators/models/blorgh/article_decorator.rb -Blorgh::Post.class_eval do +Blorgh::Article.class_eval do def time_since_created Time.current - created_at end @@ -1110,20 +1133,20 @@ end ``` ```ruby -# Blorgh/app/models/post.rb +# Blorgh/app/models/article.rb -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_many :comments end ``` -**Overriding** `Post#summary`: +**Overriding** `Article#summary`: ```ruby -# MyApp/app/decorators/models/blorgh/post_decorator.rb +# MyApp/app/decorators/models/blorgh/article_decorator.rb -Blorgh::Post.class_eval do +Blorgh::Article.class_eval do def summary "#{title} - #{truncate(text)}" end @@ -1131,9 +1154,9 @@ end ``` ```ruby -# Blorgh/app/models/post.rb +# Blorgh/app/models/article.rb -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_many :comments def summary "#{title}" @@ -1145,17 +1168,17 @@ end Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`] -(http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html). +(http://api.rubyonrails.org/classes/ActiveSupport/Concern.html). ActiveSupport::Concern manages load order of interlinked dependent modules and classes at run time allowing you to significantly modularize your code. -**Adding** `Post#time_since_created` and **Overriding** `Post#summary`: +**Adding** `Article#time_since_created` and **Overriding** `Article#summary`: ```ruby -# MyApp/app/models/blorgh/post.rb +# MyApp/app/models/blorgh/article.rb -class Blorgh::Post < ActiveRecord::Base - include Blorgh::Concerns::Models::Post +class Blorgh::Article < ActiveRecord::Base + include Blorgh::Concerns::Models::Article def time_since_created Time.current - created_at @@ -1168,22 +1191,22 @@ end ``` ```ruby -# Blorgh/app/models/post.rb +# Blorgh/app/models/article.rb -class Post < ActiveRecord::Base - include Blorgh::Concerns::Models::Post +class Article < ActiveRecord::Base + include Blorgh::Concerns::Models::Article end ``` ```ruby -# Blorgh/lib/concerns/models/post +# Blorgh/lib/concerns/models/article -module Blorgh::Concerns::Models::Post +module Blorgh::Concerns::Models::Article extend ActiveSupport::Concern # 'included do' causes the included code to be evaluated in the - # context where it is included (post.rb), rather than being - # executed in the module's context (blorgh/concerns/models/post). + # context where it is included (article.rb), rather than being + # executed in the module's context (blorgh/concerns/models/article). included do attr_accessor :author_name belongs_to :author, class_name: "User" @@ -1214,25 +1237,25 @@ When Rails looks for a view to render, it will first look in the `app/views` directory of the application. If it cannot find the view there, it will check in the `app/views` directories of all engines that have this directory. -When the application is asked to render the view for `Blorgh::PostsController`'s +When the application is asked to render the view for `Blorgh::ArticlesController`'s index action, it will first look for the path -`app/views/blorgh/posts/index.html.erb` within the application. If it cannot +`app/views/blorgh/articles/index.html.erb` within the application. If it cannot find it, it will look inside the engine. You can override this view in the application by simply creating a new file at -`app/views/blorgh/posts/index.html.erb`. Then you can completely change what +`app/views/blorgh/articles/index.html.erb`. Then you can completely change what this view would normally output. -Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` +Try this now by creating a new file at `app/views/blorgh/articles/index.html.erb` and put this content in it: ```html+erb -<h1>Posts</h1> -<%= link_to "New Post", new_post_path %> -<% @posts.each do |post| %> - <h2><%= post.title %></h2> - <small>By <%= post.author %></small> - <%= simple_format(post.text) %> +<h1>Articles</h1> +<%= link_to "New Article", new_article_path %> +<% @articles.each do |article| %> + <h2><%= article.title %></h2> + <small>By <%= article.author %></small> + <%= simple_format(article.text) %> <hr> <% end %> ``` @@ -1249,30 +1272,30 @@ Routes inside an engine are drawn on the `Engine` class within ```ruby Blorgh::Engine.routes.draw do - resources :posts + resources :articles end ``` By having isolated routes such as this, if you wish to link to an area of an engine from within an application, you will need to use the engine's routing -proxy method. Calls to normal routing methods such as `posts_path` may end up +proxy method. Calls to normal routing methods such as `articles_path` may end up going to undesired locations if both the application and the engine have such a helper defined. -For instance, the following example would go to the application's `posts_path` -if that template was rendered from the application, or the engine's `posts_path` +For instance, the following example would go to the application's `articles_path` +if that template was rendered from the application, or the engine's `articles_path` if it was rendered from the engine: ```erb -<%= link_to "Blog posts", posts_path %> +<%= link_to "Blog articles", articles_path %> ``` -To make this route always use the engine's `posts_path` routing helper method, +To make this route always use the engine's `articles_path` routing helper method, we must call the method on the routing proxy method that shares the same name as the engine. ```erb -<%= link_to "Blog posts", blorgh.posts_path %> +<%= link_to "Blog articles", blorgh.articles_path %> ``` If you wish to reference the application inside the engine in a similar way, use diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 455dc7bebe..4c02c99cc7 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -1,23 +1,24 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + 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 form control naming and their numerous attributes. Rails does away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use. +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. After reading this guide, you will know: * How to create search forms and similar kind of generic forms not representing any specific model in your application. -* How to make model-centric forms for creation and editing of specific database records. +* How to make model-centric forms for creating and editing specific database records. * How to generate select boxes from multiple types of data. -* The date and time helpers Rails provides. +* What date and time helpers Rails provides. * What makes a file upload form different. -* Some cases of building forms to external resources. +* How to post forms to external resources and specify setting an `authenticity_token`. * How to build complex forms. -------------------------------------------------------------------------------- NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit [the Rails API documentation](http://api.rubyonrails.org/) for a complete reference. - Dealing with Basic Forms ------------------------ @@ -32,18 +33,14 @@ The most basic form helper is `form_tag`. When called without arguments like this, it creates a `<form>` tag which, when submitted, will POST to the current page. For instance, assuming the current page is `/home/index`, the generated HTML will look like this (some line breaks added for readability): ```html -<form accept-charset="UTF-8" action="/home/index" method="post"> - <div style="margin:0;padding:0"> - <input name="utf8" type="hidden" value="✓" /> - <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> - </div> +<form accept-charset="UTF-8" action="/" method="post"> + <input name="utf8" type="hidden" value="✓" /> + <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" /> Form contents </form> ``` -Now, you'll notice that the HTML contains something extra: a `div` element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](./security.html#cross-site-request-forgery-csrf). - -NOTE: Throughout this guide, the `div` with the hidden input elements will be excluded from code samples for brevity. +You'll notice that the HTML contains `input` element with type `hidden`. This `input` is important, because the form cannot be successfully submitted without it. The hidden input element has name attribute of `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf). ### A Generic Search Form @@ -67,14 +64,15 @@ To create this form you will use `form_tag`, `label_tag`, `text_field_tag`, and This will generate the following HTML: ```html -<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div> +<form accept-charset="UTF-8" action="/search" method="get"> + <input name="utf8" type="hidden" value="✓" /> <label for="q">Search for:</label> <input id="q" name="q" type="text" /> <input name="commit" type="submit" value="Search" /> </form> ``` -TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. +TIP: For every form input, an ID attribute is generated from its name (`"q"` in above example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ form control in HTML. @@ -100,7 +98,15 @@ form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_ ### Helpers for Generating Form Elements -Rails provides a series of helpers for generating form elements such as checkboxes, text fields, and radio buttons. These basic helpers, with names ending in "_tag" (such as `text_field_tag` and `check_box_tag`), generate just a single `<input>` element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form data, and will make its way to the `params` hash in the controller with the value entered by the user for that field. For example, if the form contains `<%= text_field_tag(:query) %>`, then you would be able to get the value of this field in the controller with `params[:query]`. +Rails provides a series of helpers for generating form elements such as +checkboxes, text fields, and radio buttons. These basic helpers, with names +ending in `_tag` (such as `text_field_tag` and `check_box_tag`), generate just a +single `<input>` element. The first parameter to these is always the name of the +input. When the form is submitted, the name will be passed along with the form +data, and will make its way to the `params` hash in the controller with the +value entered by the user for that field. For example, if the form contains `<%= +text_field_tag(:query) %>`, then you would be able to get the value of this +field in the controller with `params[:query]`. When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in [chapter 7 of this guide](#understanding-parameter-naming-conventions). For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html). @@ -146,7 +152,7 @@ Output: <label for="age_adult">I'm over 21</label> ``` -As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one, and `params[:age]` will contain either "child" or "adult". +As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (`age`), the user will only be able to select one of them, and `params[:age]` will contain either `"child"` or `"adult"`. NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and, by expanding the clickable region, @@ -217,7 +223,7 @@ Dealing with Model Objects ### Model Object Helpers -A particularly common task for a form is editing or creating a model object. While the `*_tag` helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the _tag suffix, for example `text_field`, `text_area`. +A particularly common task for a form is editing or creating a model object. While the `*_tag` helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the `_tag` suffix, for example `text_field`, `text_area`. For these helpers the first argument is the name of an instance variable and the second is the name of a method (usually an attribute) to call on that object. Rails will set the value of the input control to the return value of that method for the object and set an appropriate input name. If your controller has defined `@person` and that person's name is Henry then a form containing: @@ -239,7 +245,7 @@ Rails provides helpers for displaying the validation errors associated with a mo ### Binding a Form to an Object -While this is an increase in comfort it is far from perfect. If Person has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what `form_for` does. +While this is an increase in comfort it is far from perfect. If `Person` has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what `form_for` does. Assume we have a controller for dealing with articles `app/controllers/articles_controller.rb`: @@ -264,12 +270,12 @@ There are a few things to note here: * `@article` is the actual object being edited. * There is a single hash of options. Routing options are passed in the `:url` hash, HTML options are passed in the `:html` hash. Also you can provide a `:namespace` option for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id. * The `form_for` method yields a **form builder** object (the `f` variable). -* Methods to create form controls are called **on** the form builder object `f` +* Methods to create form controls are called **on** the form builder object `f`. The resulting HTML is: ```html -<form accept-charset="UTF-8" action="/articles/create" method="post" class="nifty_form"> +<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" /> @@ -280,7 +286,7 @@ The name passed to `form_for` controls the key used in `params` to access the fo The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. -You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: +You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example, if you had a `Person` model with an associated `ContactDetail` model, you could create a form for creating both like so: ```erb <%= form_for @person, url: {action: "create"} do |person_form| %> @@ -294,7 +300,7 @@ 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/create" class="new_person" id="new_person" method="post"> +<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> @@ -350,7 +356,6 @@ form_for [:admin, :management, @article] For more information on Rails' routing system and the associated conventions, please see the [routing guide](routing.html). - ### How do forms with PATCH, PUT, or DELETE methods work? The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms. @@ -365,12 +370,11 @@ output: ```html <form accept-charset="UTF-8" action="/search" method="post"> - <div style="margin:0;padding:0"> - <input name="_method" type="hidden" value="patch" /> - <input name="utf8" type="hidden" value="✓" /> - <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> - </div> + <input name="_method" type="hidden" value="patch" /> + <input name="utf8" type="hidden" value="✓" /> + <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> ... +</form> ``` When parsing POSTed data, Rails will take into account the special `_method` parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example). @@ -435,14 +439,19 @@ output: Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option. -TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` - you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. +TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer `2` you cannot pass `"2"` to `options_for_select` - you must pass `2`. Be aware of values extracted from the `params` hash as they are all strings. WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true. You can add arbitrary attributes to the options using hashes: ```html+erb -<%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %> +<%= options_for_select( + [ + ['Lisbon', 1, { 'data-size' => '2.8 million' }], + ['Madrid', 2, { 'data-size' => '3.2 million' }] + ], 2 +) %> output: @@ -474,11 +483,21 @@ As with other helpers, if you were to use the `select` helper on a form builder <%= f.select(:city_id, ...) %> ``` -WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. +You can also pass a block to `select` helper: + +```erb +<%= f.select(:city_id) do %> + <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%> + <%= content_tag(:option, c.first, value: c.last) %> + <% end %> +<% end %> +``` + +WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of `ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. ### Option Tags from a Collection of Arbitrary Objects -Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them: +Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a `City` model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them: ```erb <% cities_array = City.all.map { |city| [city.name, city.id] } %> @@ -497,6 +516,12 @@ As the name implies, this only generates option tags. To generate a working sele <%= collection_select(:person, :city_id, City.all, :id, :name) %> ``` +As with other helpers, if you were to use the `collection_select` helper on a form builder scoped to the `@person` object, the syntax would be: + +```erb +<%= f.collection_select(:city_id, City.all, :id, :name) %> +``` + To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`. NOTE: Pairs passed to `options_for_select` should have the name first and the id second, however with `options_from_collection_for_select` the first argument is the value method and the second the text method. @@ -525,7 +550,7 @@ Both of these families of helpers will create a series of select boxes for the d ### Barebones Helpers -The `select_*` family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example +The `select_*` family of helpers take as their first argument an instance of `Date`, `Time` or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example: ```erb <%= select_date Date.today, prefix: :start_date %> @@ -539,7 +564,7 @@ outputs (with actual option values omitted for brevity) <select id="start_date_day" name="start_date[day]"> ... </select> ``` -The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example +The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time` or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example: ```ruby Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) @@ -582,9 +607,9 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the ### Individual Components -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example, "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. -The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example +The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time` or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example: ```erb <%= select_year(2009) %> @@ -614,7 +639,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the ### What Gets Uploaded -The object in the `params` hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). +The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a `StringIO` or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). ```ruby def upload @@ -625,7 +650,7 @@ def upload end ``` -Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are [CarrierWave](https://github.com/jnicklas/carrierwave) and [Paperclip](http://www.thoughtbot.com/projects/paperclip). +Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are [CarrierWave](https://github.com/jnicklas/carrierwave) and [Paperclip](https://github.com/thoughtbot/paperclip). NOTE: If the user has not selected a file the corresponding parameter will be an empty string. @@ -636,7 +661,7 @@ Unlike other forms making an asynchronous file upload form is not as simple as p Customizing Form Builders ------------------------- -As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass FormBuilder and add the helpers there. For example +As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of `FormBuilder` (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass `FormBuilder` and add the helpers there. For example: ```erb <%= form_for @person do |f| %> @@ -652,7 +677,7 @@ can be replaced with <% end %> ``` -by defining a LabellingFormBuilder class similar to the following: +by defining a `LabellingFormBuilder` class similar to the following: ```ruby class LabellingFormBuilder < ActionView::Helpers::FormBuilder @@ -670,17 +695,17 @@ The form builder used also determines what happens when you do <%= render partial: f %> ``` -If `f` is an instance of FormBuilder then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the `labelling_form` partial would be rendered instead. +If `f` is an instance of `FormBuilder` then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class `LabellingFormBuilder` then the `labelling_form` partial would be rendered instead. Understanding Parameter Naming Conventions ------------------------------------------ -As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard `create` +As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example, in a standard `create` action for a Person model, `params[:person]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. -TIP: You may find you can try out examples in this section faster by using the console to directly invoke Racks' parameter parser. For example, +TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rack's parameter parser. For example, ```ruby Rack::Utils.parse_query "name=fred&phone=0123456789" @@ -689,7 +714,7 @@ Rack::Utils.parse_query "name=fred&phone=0123456789" ### Basic Structures -The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in `params`. For example if a form contains +The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in `params`. For example, if a form contains: ```html <input id="person_name" name="person[name]" type="text" value="Henry"/> @@ -703,7 +728,7 @@ the `params` hash will contain and `params[:person][:name]` will retrieve the submitted value in the controller. -Hashes can be nested as many levels as required, for example +Hashes can be nested as many levels as required, for example: ```html <input id="person_address_city" name="person[address][city]" type="text" value="New York"/> @@ -715,7 +740,7 @@ will result in the `params` hash being {'person' => {'address' => {'city' => 'New York'}}} ``` -Normally Rails ignores duplicate parameter names. If the parameter name contains an empty set of square brackets [] then they will be accumulated in an array. If you wanted people to be able to input multiple phone numbers, you could place this in the form: +Normally Rails ignores duplicate parameter names. If the parameter name contains an empty set of square brackets `[]` then they will be accumulated in an array. If you wanted users to be able to input multiple phone numbers, you could place this in the form: ```html <input name="person[phone_number][]" type="text"/> @@ -723,11 +748,11 @@ Normally Rails ignores duplicate parameter names. If the parameter name contains <input name="person[phone_number][]" type="text"/> ``` -This would result in `params[:person][:phone_number]` being an array. +This would result in `params[:person][:phone_number]` being an array containing the inputted phone numbers. ### Combining Them -We can mix and match these two concepts. For example, one element of a hash might be an array as in the previous example, or you can have an array of hashes. For example a form might let you create any number of addresses by repeating the following form fragment +We can mix and match these two concepts. One element of a hash might be an array as in the previous example, or you can have an array of hashes. For example, a form might let you create any number of addresses by repeating the following form fragment ```html <input name="addresses[][line1]" type="text"/> @@ -737,7 +762,7 @@ We can mix and match these two concepts. For example, one element of a hash migh This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash. -There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter. +There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can usually be replaced by hashes; for example, instead of having an array of model objects, one can have a hash of model objects keyed by their id, an array index or some other parameter. WARNING: Array parameters do not play well with the `check_box` helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The `check_box` helper fakes this by creating an auxiliary hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use `check_box_tag` or to use hashes instead of arrays. @@ -809,21 +834,21 @@ As a shortcut you can append [] to the name and omit the `:index` option. This i produces exactly the same output as the previous example. -Forms to external resources +Forms to External Resources --------------------------- -If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an `authenticity_token` for this resource. You can do it by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options: +Rails' form helpers can also be used to build a form for posting data to an external resource. However, at times it can be necessary to set an `authenticity_token` for the resource; this can be done by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options: ```erb -<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %> +<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token' do %> Form contents <% end %> ``` -Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an `authenticity_token` hidden field at all. For doing this just pass `false` to the `:authenticity_token` option: +Sometimes when submitting data to an external resource, like a payment gateway, the fields that can be used in the form are limited by an external API and it may be undesirable to generate an `authenticity_token`. To not send a token, simply pass `false` to the `:authenticity_token` option: ```erb -<%= form_tag 'http://farfar.away/form', authenticity_token: false) do %> +<%= form_tag 'http://farfar.away/form', authenticity_token: false do %> Form contents <% end %> ``` @@ -847,7 +872,7 @@ Or if you don't want to render an `authenticity_token` field: Building Complex Forms ---------------------- -Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. +Many apps grow beyond simple forms editing a single object. For example, when creating a `Person` you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. ### Configuring the Model @@ -899,7 +924,7 @@ end ``` The `fields_for` yields a form builder. The parameters' name will be what -`accepts_nested_attributes_for` expects. For example when creating a user with +`accepts_nested_attributes_for` expects. For example, when creating a user with 2 addresses, the submitted parameters would look like: ```ruby @@ -998,4 +1023,4 @@ As a convenience you can instead pass the symbol `:all_blank` which will create ### Adding Fields on the Fly -Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice. +Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any built-in support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice. diff --git a/guides/source/generators.md b/guides/source/generators.md index 4a5377c206..14f451cbc9 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Creating and Customizing Rails Generators & Templates ===================================================== @@ -8,6 +10,7 @@ After reading this guide, you will know: * How to see which generators are available in your application. * How to create a generator using templates. * How Rails searches for generators before invoking them. +* How Rails internally generates Rails code from the templates. * How to customize your scaffold by creating new generators. * How to customize your scaffold by changing generator templates. * How to use fallbacks to avoid overwriting a huge set of generators. @@ -23,19 +26,19 @@ When you create an application using the `rails` command, you are in fact using ```bash $ rails new myapp $ cd myapp -$ rails generate +$ bin/rails generate ``` You will get a list of all generators that comes with Rails. If you need a detailed description of the helper generator, for example, you can simply do: ```bash -$ rails generate helper --help +$ bin/rails generate helper --help ``` Creating Your First Generator ----------------------------- -Since Rails 3.0, generators are built on top of [Thor](https://github.com/erikhuda/thor). Thor provides powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named `initializer.rb` inside `config/initializers`. +Since Rails 3.0, generators are built on top of [Thor](https://github.com/erikhuda/thor). Thor provides powerful options for parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named `initializer.rb` inside `config/initializers`. The first step is to create a file at `lib/generators/initializer_generator.rb` with the following content: @@ -54,13 +57,13 @@ Our new generator is quite simple: it inherits from `Rails::Generators::Base` an To invoke our new generator, we just need to do: ```bash -$ rails generate initializer +$ bin/rails generate initializer ``` Before we go on, let's see our brand new generator description: ```bash -$ rails generate initializer --help +$ bin/rails generate initializer --help ``` Rails is usually able to generate good descriptions if a generator is namespaced, as `ActiveRecord::Generators::ModelGenerator`, but not in this particular case. We can solve this problem in two ways. The first one is calling `desc` inside our generator: @@ -82,7 +85,7 @@ Creating Generators with Generators Generators themselves have a generator: ```bash -$ rails generate generator initializer +$ bin/rails generate generator initializer create lib/generators/initializer create lib/generators/initializer/initializer_generator.rb create lib/generators/initializer/USAGE @@ -102,7 +105,7 @@ First, notice that we are inheriting from `Rails::Generators::NamedBase` instead We can see that by invoking the description of this new generator (don't forget to delete the old generator file): ```bash -$ rails generate initializer --help +$ bin/rails generate initializer --help Usage: rails generate initializer NAME [options] ``` @@ -130,7 +133,7 @@ end And let's execute our generator: ```bash -$ rails generate initializer core_extensions +$ bin/rails generate initializer core_extensions ``` We can see that now an initializer named core_extensions was created at `config/initializers/core_extensions.rb` with the contents of our template. That means that `copy_file` copied a file in our source root to the destination path we gave. The method `file_name` is automatically created when we inherit from `Rails::Generators::NamedBase`. @@ -169,7 +172,7 @@ end Before we customize our workflow, let's first see what our scaffold looks like: ```bash -$ rails generate scaffold User name:string +$ bin/rails generate scaffold User name:string invoke active_record create db/migrate/20130924151154_create_users.rb create app/models/user.rb @@ -191,23 +194,21 @@ $ rails generate scaffold User name:string create test/controllers/users_controller_test.rb invoke helper create app/helpers/users_helper.rb - invoke test_unit - create test/helpers/users_helper_test.rb invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/users.js.coffee + create app/assets/javascripts/users.coffee invoke scss - create app/assets/stylesheets/users.css.scss + create app/assets/stylesheets/users.scss invoke scss - create app/assets/stylesheets/scaffolds.css.scss + create app/assets/stylesheets/scaffolds.scss ``` Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. -Our first customization on the workflow will be to stop generating stylesheets, javascripts and test fixtures for scaffolds. We can achieve that by changing our configuration to the following: +Our first customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds. We can achieve that by changing our configuration to the following: ```ruby config.generators do |g| @@ -219,12 +220,12 @@ config.generators do |g| end ``` -If we generate another resource with the scaffold generator, we can see that stylesheets, javascripts and fixtures are not created anymore. If you want to customize it further, for example to use DataMapper and RSpec instead of Active Record and TestUnit, it's just a matter of adding their gems to your application and configuring your generators. +If we generate another resource with the scaffold generator, we can see that stylesheet, JavaScript and fixture files are not created anymore. If you want to customize it further, for example to use DataMapper and RSpec instead of Active Record and TestUnit, it's just a matter of adding their gems to your application and configuring your generators. To demonstrate this, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator within the rails namespace, as this is where rails searches for generators used as hooks: ```bash -$ rails generate generator rails/my_helper +$ bin/rails generate generator rails/my_helper create lib/generators/rails/my_helper create lib/generators/rails/my_helper/my_helper_generator.rb create lib/generators/rails/my_helper/USAGE @@ -248,10 +249,10 @@ end end ``` -We can try out our new generator by creating a helper for users: +We can try out our new generator by creating a helper for products: ```bash -$ rails generate my_helper products +$ bin/rails generate my_helper products create app/helpers/products_helper.rb ``` @@ -279,10 +280,10 @@ end and see it in action when invoking the generator: ```bash -$ rails generate scaffold Post body:text +$ bin/rails generate scaffold Article body:text [...] invoke my_helper - create app/helpers/posts_helper.rb + create app/helpers/articles_helper.rb ``` We can notice on the output that our new helper was invoked instead of the Rails default. However one thing is missing, which is tests for our new generator and to do that, we are going to reuse old helpers test generators. @@ -342,6 +343,22 @@ end If you generate another resource, you can see that we get exactly the same result! This is useful if you want to customize your scaffold templates and/or layout by just creating `edit.html.erb`, `index.html.erb` and so on inside `lib/templates/erb/scaffold`. +Scaffold templates in Rails frequently use ERB tags; these tags need to be +escaped so that the generated output is valid ERB code. + +For example, the following escaped ERB tag would be needed in the template +(note the extra `%`)... + +```ruby +<%%= stylesheet_include_tag :application %> +``` + +...to generate the following output: + +```ruby +<%= stylesheet_include_tag :application %> +``` + Adding Generators Fallbacks --------------------------- @@ -365,7 +382,7 @@ end Now, if you create a Comment scaffold, you will see that the shoulda generators are being invoked, and at the end, they are just falling back to TestUnit generators: ```bash -$ rails generate scaffold Comment body:text +$ bin/rails generate scaffold Comment body:text invoke active_record create db/migrate/20130924143118_create_comments.rb create app/models/comment.rb @@ -387,14 +404,12 @@ $ rails generate scaffold Comment body:text create test/controllers/comments_controller_test.rb invoke my_helper create app/helpers/comments_helper.rb - invoke shoulda - create test/helpers/comments_helper_test.rb invoke jbuilder create app/views/comments/index.json.jbuilder create app/views/comments/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/comments.js.coffee + create app/assets/javascripts/comments.coffee invoke scss ``` @@ -507,7 +522,7 @@ Replaces text inside a file. gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code' ``` -Regular Expressions can be used to make this method more precise. You can also use append_file and prepend_file in the same way to place code at the beginning and end of a file respectively. +Regular Expressions can be used to make this method more precise. You can also use `append_file` and `prepend_file` in the same way to place code at the beginning and end of a file respectively. ### `application` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index dbcedba800..fecd006a5f 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Getting Started with Rails ========================== @@ -21,19 +23,22 @@ 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](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer -* The [RubyGems](http://rubygems.org) packaging system - * To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org) -* A working installation of the [SQLite3 Database](http://www.sqlite.org) +* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. +* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby + versions 1.9 and later. 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). 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 -curve diving straight into Rails. There are some good free resources on the -Internet for learning Ruby, including: +curve diving straight into Rails. There are several curated lists of online resources +for learning Ruby: + +* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/) +* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby) -* [Mr. Neighborly's Humble Little Ruby Book](http://www.humblelittlerubybook.com) -* [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/) -* [Why's (Poignant) Guide to Ruby](http://mislav.uniqpath.com/poignant-guide/) +Be aware that some resources, while still excellent, cover versions of Ruby as old as +1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day +development with Rails. What is Rails? -------------- @@ -54,28 +59,28 @@ learned elsewhere, you may have a less happy experience. The Rails philosophy includes two major guiding principles: -* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and - over again is a bad thing. -* Convention Over Configuration - means that Rails makes assumptions about what - you want to do and how you're going to do it, rather than requiring you to - specify every little thing through endless configuration files. +* **Don't Repeat Yourself:** DRY is a principle of software development which + states that "Every piece of knowledge must have a single, unambiguous, authoritative + representation within a system." By not writing the same information over and over + again, our code is more maintainable, more extensible, and less buggy. +* **Convention Over Configuration:** Rails has opinions about the best way to do many + things in a web application, and defaults to this set of conventions, rather than + require that you specify every minutiae through endless configuration files. Creating a New Rails Project ---------------------------- The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can -literally follow along step by step. You can get the complete code -[here](https://github.com/rails/docrails/tree/master/guides/code/getting_started). +literally follow along step by step. By following along with this guide, you'll create a Rails project called -`blog`, a -(very) simple weblog. Before you can start building the application, you need to -make sure that you have Rails itself installed. +`blog`, a (very) simple weblog. Before you can start building the application, +you need to make sure that you have Rails itself installed. -TIP: The examples below use `#` and `$` to denote superuser and regular -user terminal prompts respectively in a UNIX-like OS. If you are using -Windows, your prompt will look something like `c:\source_code>` +TIP: The examples below use `$` to represent your terminal prompt in a UNIX-like OS, +though it may have been customized to appear differently. If you are using Windows, +your prompt will look something like `c:\source_code>` ### Installing Rails @@ -84,21 +89,35 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed: +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). +For more installation methods for most Operating Systems take a look at +[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). + ```bash $ ruby -v ruby 2.0.0p353 ``` +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 +at the [SQLite3 website](https://www.sqlite.org). +Verify that it is correctly installed and in your PATH: + +```bash +$ sqlite3 --version +``` + +The program should report its version. + To install Rails, use the `gem install` command provided by RubyGems: ```bash $ gem install rails ``` -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 [Rails One Click](http://railsoneclick.com). - To verify that you have everything installed correctly, you should be able to run the following: @@ -106,7 +125,7 @@ run the following: $ rails --version ``` -If it says something like "Rails 4.1.0", you are ready to continue. +If it says something like "Rails 5.0.0", you are ready to continue. ### Creating the Blog Application @@ -143,20 +162,20 @@ of the files and folders that Rails created by default: | File/Folder | Purpose | | ----------- | ------- | -|app|Contains the controllers, models, views, helpers, mailers 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 deploy or run your application.| +|app/|Contains the controllers, models, views, helpers, mailers 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, 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.| -|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://gembundler.com).| -|lib|Extended modules for your application.| -|log|Application log files.| -|public|The only folder seen by the world as-is. Contains static files and compiled assets.| +|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).| +|lib/|Extended modules for your application.| +|log/|Application log files.| +|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.| |README.rdoc|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, pid, and session files).| -|vendor|A place for all third-party code. In a typical Rails application this includes vendored gems.| +|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.| Hello, Rails! ------------- @@ -171,23 +190,24 @@ start a web server on your development machine. You can do this by running the following in the `blog` directory: ```bash -$ rails server +$ bin/rails server ``` -TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the -absence of a runtime will give you an `execjs` error. Usually Mac OS X and -Windows come with a JavaScript runtime installed. Rails adds the `therubyracer` -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 about all the supported runtimes at -[ExecJS](https://github.com/sstephenson/execjs#readme). +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 +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 +all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). This will fire up WEBrick, a web server distributed with Ruby by default. To see your application in action, open a browser window and navigate to <http://localhost:3000>. You should see the Rails default information page: -![Welcome aboard screenshot](images/getting_started/rails_welcome.jpg) +![Welcome aboard screenshot](images/getting_started/rails_welcome.png) 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 @@ -224,7 +244,7 @@ tell it you want a controller called "welcome" with an action called "index", just like this: ```bash -$ rails generate controller welcome index +$ bin/rails generate controller welcome index ``` Rails will create several files and a route for you. @@ -239,17 +259,16 @@ invoke test_unit create test/controllers/welcome_controller_test.rb invoke helper create app/helpers/welcome_helper.rb -invoke test_unit -create test/helpers/welcome_helper_test.rb invoke assets invoke coffee -create app/assets/javascripts/welcome.js.coffee +create app/assets/javascripts/welcome.coffee invoke scss -create app/assets/stylesheets/welcome.css.scss +create app/assets/stylesheets/welcome.scss ``` -Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb` -and the view, located at `app/views/welcome/index.html.erb`. +Most important of these are of course the controller, located at +`app/controllers/welcome_controller.rb` and the view, located at +`app/views/welcome/index.html.erb`. Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all of the existing code in the file, and replace it with the following single line @@ -283,8 +302,9 @@ Rails.application.routes.draw do # ... ``` -This is your application's _routing file_ which holds entries in a special DSL -(domain-specific language) that tells Rails how to connect incoming requests to +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) +that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root` and @@ -315,18 +335,19 @@ Now that you've seen how to create a controller, an action and a view, let's create something with a bit more substance. In the Blog application, you will now create a new _resource_. A resource is the -term used for a collection of similar objects, such as posts, people or animals. +term used for a collection of similar objects, such as articles, people or +animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. Rails provides a `resources` method which can be used to declare a standard REST -resource. Here's what `config/routes.rb` should look like after the _post resource_ -is declared. +resource. You need to add the _article resource_ to the +`config/routes.rb` as follows: ```ruby -Blog::Application.routes.draw do +Rails.application.routes.draw do - resources :posts + resources :articles root 'welcome#index' end @@ -335,110 +356,114 @@ end If you run `rake routes`, you'll see that it has defined routes for all the standard RESTful actions. The meaning of the prefix column (and other columns) will be seen later, but for now notice that Rails has inferred the -singular form `post` and makes meaningful use of the distinction. +singular form `article` and makes meaningful use of the distinction. ```bash -$ rake routes - Prefix Verb URI Pattern Controller#Action - posts GET /posts(.:format) posts#index - POST /posts(.:format) posts#create - new_post GET /posts/new(.:format) posts#new -edit_post GET /posts/:id/edit(.:format) posts#edit - post GET /posts/:id(.:format) posts#show - PATCH /posts/:id(.:format) posts#update - PUT /posts/:id(.:format) posts#update - DELETE /posts/:id(.:format) posts#destroy - root / welcome#index -``` - -In the next section, you will add the ability to create new posts in your +$ bin/rake 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 +``` + +In the next section, you will add the ability to create new articles in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this: -![The new post form](images/getting_started/new_post.png) +![The new article form](images/getting_started/new_article.png) It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. ### Laying down the ground work -The first thing that you are going to need to create a new post within the -application is a place to do that. A great place for that would be at `/posts/new`. -With the route already defined, requests can now be made to `/posts/new` in the -application. Navigate to <http://localhost:3000/posts/new> and you'll see a -routing error: +Firstly, you need a place within the application to create a new article. A +great place for that would be at `/articles/new`. With the route already +defined, requests can now be made to `/articles/new` in the application. +Navigate to <http://localhost:3000/articles/new> and you'll see a routing +error: -![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png) +![Another routing error, uninitialized constant ArticlesController](images/getting_started/routing_error_no_controller.png) This error occurs because the route needs to have a controller defined in order to serve the request. The solution to this particular problem is simple: create -a controller called `PostsController`. You can do this by running this command: +a controller called `ArticlesController`. You can do this by running this +command: ```bash -$ rails g controller posts +$ bin/rails g controller articles ``` -If you open up the newly generated `app/controllers/posts_controller.rb` you'll -see a fairly empty controller: +If you open up the newly generated `app/controllers/articles_controller.rb` +you'll see a fairly empty controller: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController end ``` -A controller is simply a class that is defined to inherit from `ApplicationController`. +A controller is simply a class that is defined to inherit from +`ApplicationController`. It's inside this class that you'll define methods that will become the actions -for this controller. These actions will perform CRUD operations on the posts +for this controller. These actions will perform CRUD operations on the articles within our system. -NOTE: There are `public`, `private` and `protected` methods in Ruby, +NOTE: There are `public`, `private` and `protected` methods in Ruby, but only `public` methods can be actions for controllers. For more details check out [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/). -If you refresh <http://localhost:3000/posts/new> now, you'll get a new error: +If you refresh <http://localhost:3000/articles/new> now, you'll get a new error: -![Unknown action new for PostsController!](images/getting_started/unknown_action_new_for_posts.png) +![Unknown action new for ArticlesController!](images/getting_started/unknown_action_new_for_articles.png) -This error indicates that Rails cannot find the `new` action inside the `PostsController` -that you just generated. This is because when controllers are generated in Rails -they are empty by default, unless you tell it your wanted actions during the -generation process. +This error indicates that Rails cannot find the `new` action inside the +`ArticlesController` that you just generated. This is because when controllers +are generated in Rails they are empty by default, unless you tell it +your desired actions during the generation process. To manually define an action inside a controller, all you need to do is to -define a new method inside the controller. Open `app/controllers/posts_controller.rb` -and inside the `PostsController` class, define a `new` method like this: +define a new method inside the controller. Open +`app/controllers/articles_controller.rb` and inside the `ArticlesController` +class, define the `new` method so that your controller now looks like this: ```ruby -def new +class ArticlesController < ApplicationController + def new + end end ``` -With the `new` method defined in `PostsController`, if you refresh <http://localhost:3000/posts/new> -you'll see another error: +With the `new` method defined in `ArticlesController`, if you refresh +<http://localhost:3000/articles/new> you'll see another error: -![Template is missing for posts/new](images/getting_started/template_is_missing_posts_new.png) +![Template is missing for articles/new] +(images/getting_started/template_is_missing_articles_new.png) You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view -available, Rails errors out. +available, Rails will raise an exception. In the above image, the bottom line has been truncated. Let's see what the full -thing looks like: +error message looks like: -<blockquote> -Missing template posts/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" -</blockquote> +>Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" That's quite a lot of text! Let's quickly go through and understand what each -part of it does. +part of it means. -The first part identifies what template is missing. In this case, it's the -`posts/new` template. Rails will first look for this template. If not found, +The first part identifies which template is missing. In this case, it's the +`articles/new` template. Rails will first look for this template. If not found, then it will attempt to load a template called `application/new`. It looks for -one here because the `PostsController` inherits from `ApplicationController`. +one here because the `ArticlesController` inherits from `ApplicationController`. The next part of the message contains a hash. The `:locale` key in this hash -simply indicates what spoken language template should be retrieved. By default, +simply indicates which spoken language template should be retrieved. By default, this is the English - or "en" - template. The next key, `:formats` specifies the format of template to be served in response. The default format is `:html`, and so Rails is looking for an HTML template. The final key, `:handlers`, is telling @@ -451,34 +476,37 @@ Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths. The simplest template that would work in this case would be one located at -`app/views/posts/new.html.erb`. The extension of this file name is key: the -first extension is the _format_ of the template, and the second extension is the -_handler_ that will be used. Rails is attempting to find a template called -`posts/new` within `app/views` for the application. The format for this template -can only be `html` and the handler must be one of `erb`, `builder` or `coffee`. -Because you want to create a new HTML form, you will be using the `ERB` -language. Therefore the file should be called `posts/new.html.erb` and needs to -be located inside the `app/views` directory of the application. - -Go ahead now and create a new file at `app/views/posts/new.html.erb` and write -this content in it: +`app/views/articles/new.html.erb`. The extension of this file name is important: +the first extension is the _format_ of the template, and the second extension +is the _handler_ that will be used. Rails is attempting to find a template +called `articles/new` within `app/views` for the application. The format for +this template can only be `html` and the handler must be one of `erb`, +`builder` or `coffee`. Because you want to create a new HTML form, you will be +using the `ERB` language which is designed to embed Ruby in HTML. + +Therefore the file should be called `articles/new.html.erb` and needs to be +located inside the `app/views` directory of the application. + +Go ahead now and create a new file at `app/views/articles/new.html.erb` and +write this content in it: ```html -<h1>New Post</h1> +<h1>New Article</h1> ``` -When you refresh <http://localhost:3000/posts/new> you'll now see that the page -has a title. The route, controller, action and view are now working -harmoniously! It's time to create the form for a new post. +When you refresh <http://localhost:3000/articles/new> you'll now see that the +page has a title. The route, controller, action and view are now working +harmoniously! It's time to create the form for a new article. ### The first form -To create a form within this template, you will use a <em>form -builder</em>. The primary form builder for Rails is provided by a helper -method called `form_for`. To use this method, add this code into `app/views/posts/new.html.erb`: +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 +`app/views/articles/new.html.erb`: ```html+erb -<%= form_for :post do |f| %> +<%= form_for :article do |f| %> <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -499,71 +527,72 @@ If you refresh the page now, you'll see the exact same form as in the example. 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 `:post`. This tells the `form_for` +form. In this case, it's the symbol `:article`. This tells the `form_for` 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 -text fields, one each for the title and text of a post. Finally, a call to +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. 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` -attribute for the form is pointing at `/posts/new`. This is a problem because +attribute for the form is pointing at `/articles/new`. This is a problem because 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 post. +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`. 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/posts/new.html.erb` to look like this: +Edit the `form_for` line inside `app/views/articles/new.html.erb` to look like +this: ```html+erb -<%= form_for :post, url: posts_path do |f| %> +<%= form_for :article, url: articles_path do |f| %> ``` -In this example, the `posts_path` helper is passed to the `:url` option. +In this example, the `articles_path` helper is passed to the `:url` option. To see what Rails will do with this, we look back at the output of `rake routes`: ```bash -$ rake routes - Prefix Verb URI Pattern Controller#Action - posts GET /posts(.:format) posts#index - POST /posts(.:format) posts#create - new_post GET /posts/new(.:format) posts#new -edit_post GET /posts/:id/edit(.:format) posts#edit - post GET /posts/:id(.:format) posts#show - PATCH /posts/:id(.:format) posts#update - PUT /posts/:id(.:format) posts#update - DELETE /posts/:id(.:format) posts#destroy - root / welcome#index -``` - -The `posts_path` helper tells Rails to point the form -to the URI Pattern associated with the `posts` prefix; and -the form will (by default) send a `POST` request -to that route. This is associated with the -`create` action of the current controller, the `PostsController`. +$ bin/rake 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 +``` + +The `articles_path` helper tells Rails to point the form to the URI Pattern +associated with the `articles` prefix; and the form will (by default) send a +`POST` request to that route. This is associated with the `create` action of +the current controller, the `ArticlesController`. With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new -post, so go ahead and do that. When you submit the form, you should see a +article, so go ahead and do that. When you submit the form, you should see a familiar error: -![Unknown action create for PostsController](images/getting_started/unknown_action_create_for_posts.png) +![Unknown action create for ArticlesController] +(images/getting_started/unknown_action_create_for_articles.png) -You now need to create the `create` action within the `PostsController` for this -to work. +You now need to create the `create` action within the `ArticlesController` for +this to work. -### Creating posts +### Creating articles To make the "Unknown action" go away, you can define a `create` action within -the `PostsController` class in `app/controllers/posts_controller.rb`, underneath -the `new` action: +the `ArticlesController` class in `app/controllers/articles_controller.rb`, +underneath the `new` action, as shown: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController def new end @@ -574,7 +603,7 @@ end If you re-submit the form now, you'll see another familiar error: a template is missing. That's ok, we can ignore that for now. What the `create` action should -be doing is saving our new post to a database. +be doing is saving our new article to the database. When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller @@ -583,105 +612,104 @@ look like, change the `create` action to this: ```ruby def create - render text: params[:post].inspect + render plain: params[:article].inspect end ``` -The `render` method here is taking a very simple hash with a key of `text` and -value of `params[:post].inspect`. The `params` method is the object which +The `render` method here is taking a very simple hash with a key of `plain` and +value of `params[:article].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. +TIP: Ensure you have a firm grasp of the `params` method, as you'll use it fairly regularly. Let's consider an example URL: **http://www.example.com/?username=dhh&email=dhh@email.com**. In this URL, `params[:username]` would equal "dhh" and `params[:email]` would equal "dhh@email.com". + If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following: ```ruby -{"title"=>"First post!", "text"=>"This is my first post."} +{"title"=>"First article!", "text"=>"This is my first article."} ``` -This action is now displaying the parameters for the post that are coming in +This action is now displaying the parameters for the article that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them. -### Creating the Post model +### Creating the Article model -Models in Rails use a singular name, and their corresponding database tables use -a plural name. Rails provides a generator for creating models, which -most Rails developers tend to use when creating new models. -To create the new model, run this command in your terminal: +Models in Rails use a singular name, and their corresponding database tables +use a plural name. Rails provides a generator for creating models, which most +Rails developers tend to use when creating new models. To create the new model, +run this command in your terminal: ```bash -$ rails generate model Post title:string text:text +$ bin/rails generate model Article title:string text:text ``` -With that command we told Rails that we want a `Post` model, together +With that command we told Rails that we want a `Article` model, together with a _title_ attribute of type string, and a _text_ attribute -of type text. Those attributes are automatically added to the `posts` -table in the database and mapped to the `Post` model. +of type text. Those attributes are automatically added to the `articles` +table in the database and mapped to the `Article` model. -Rails responded by creating a bunch of files. For -now, we're only interested in `app/models/post.rb` and -`db/migrate/20120419084633_create_posts.rb` (your name could be a bit -different). The latter is responsible -for creating the database structure, which is what we'll look at next. +Rails responded by creating a bunch of files. For now, we're only interested +in `app/models/article.rb` and `db/migrate/20140120191729_create_articles.rb` +(your name could be a bit different). The latter is responsible for creating +the database structure, which is what we'll look at next. -TIP: Active Record is smart enough to automatically map column names to -model attributes, which means you don't have to declare attributes -inside Rails models, as that will be done automatically by Active -Record. +TIP: Active Record is smart enough to automatically map column names to model +attributes, which means you don't have to declare attributes inside Rails +models, as that will be done automatically by Active Record. ### Running a Migration -As we've just seen, `rails generate model` created a _database -migration_ file inside the `db/migrate` directory. -Migrations are Ruby classes that are designed to make it simple to -create and modify database tables. Rails uses rake commands to run migrations, -and it's possible to undo a migration after it's been applied to your database. -Migration filenames include a timestamp to ensure that they're processed in the -order that they were created. +As we've just seen, `rails generate model` created a _database migration_ file +inside the `db/migrate` directory. Migrations are Ruby classes that are +designed to make it simple to create and modify database tables. Rails uses +rake commands to run migrations, and it's possible to undo a migration after +it's been applied to your database. Migration filenames include a timestamp to +ensure that they're processed in the order that they were created. -If you look in the `db/migrate/20120419084633_create_posts.rb` file (remember, -yours will have a slightly different name), here's what you'll find: +If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file +(remember, yours will have a slightly different name), here's what you'll find: ```ruby -class CreatePosts < ActiveRecord::Migration +class CreateArticles < ActiveRecord::Migration def change - create_table :posts do |t| + create_table :articles do |t| t.string :title t.text :text - t.timestamps + t.timestamps null: false end end end ``` -The above migration creates a method named `change` which will be called when you -run this migration. The action defined in this method is also reversible, which -means Rails knows how to reverse the change made by this migration, in case you -want to reverse it later. When you run this migration it will create a -`posts` table with one string column and a text column. It also creates two -timestamp fields to allow Rails to track post creation and update times. +The above migration creates a method named `change` which will be called when +you run this migration. The action defined in this method is also reversible, +which means Rails knows how to reverse the change made by this migration, +in case you want to reverse it later. When you run this migration it will create +an `articles` table with one string column and a text column. It also creates +two timestamp fields to allow Rails to track article creation and update times. -TIP: For more information about migrations, refer to [Rails Database -Migrations](migrations.html). +TIP: For more information about migrations, refer to [Rails Database Migrations] +(migrations.html). At this point, you can use a rake command to run the migration: ```bash -$ rake db:migrate +$ bin/rake db:migrate ``` -Rails will execute this migration command and tell you it created the Posts +Rails will execute this migration command and tell you it created the Articles table. ```bash -== CreatePosts: migrating ==================================================== --- create_table(:posts) +== CreateArticles: migrating ================================================== +-- create_table(:articles) -> 0.0019s -== CreatePosts: migrated (0.0020s) =========================================== +== CreateArticles: migrated (0.0020s) ========================================= ``` NOTE. Because you're working in the development environment by default, this @@ -692,137 +720,183 @@ invoking the command: `rake db:migrate RAILS_ENV=production`. ### Saving data in the controller -Back in `PostsController`, we need to change the `create` action -to use the new `Post` model to save the data in the database. Open `app/controllers/posts_controller.rb` -and change the `create` action to look like this: +Back in `ArticlesController`, we need to change the `create` action +to use the new `Article` model to save the data in the database. +Open `app/controllers/articles_controller.rb` and change the `create` action to +look like this: ```ruby def create - @post = Post.new(params[:post]) + @article = Article.new(params[:article]) - @post.save - redirect_to @post + @article.save + redirect_to @article end ``` Here's what's going on: every Rails model can be initialized with its respective attributes, which are automatically mapped to the respective -database columns. In the first line we do just that -(remember that `params[:post]` contains the attributes we're interested in). -Then, `@post.save` is responsible for saving the model in the database. -Finally, we redirect the user to the `show` action, which we'll define later. +database columns. In the first line we do just that (remember that +`params[:article]` contains the attributes we're interested in). Then, +`@article.save` is responsible for saving the model in the database. Finally, +we redirect the user to the `show` action, which we'll define later. -TIP: As we'll see later, `@post.save` returns a boolean indicating -whether the model was saved or not. +TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `app/models/article.rb`. Class names in Ruby must begin with a capital letter. -If you now go to -<http://localhost:3000/posts/new> you'll *almost* be able to create a post. Try -it! You should get an error that looks like this: +TIP: As we'll see later, `@article.save` returns a boolean indicating whether +the article was saved or not. -![Forbidden attributes for new post](images/getting_started/forbidden_attributes_for_new_post.png) +If you now go to <http://localhost:3000/articles/new> you'll *almost* be able +to create an article. Try it! You should get an error that looks like this: + +![Forbidden attributes for new article] +(images/getting_started/forbidden_attributes_for_new_article.png) Rails has several security features that help you write secure applications, -and you're running into one of them now. This one is called -`strong_parameters`, which requires us to tell Rails exactly which parameters -we want to accept in our controllers. In this case, we want to allow the -`title` and `text` parameters, so change your `create` controller action to -look like this: +and you're running into one of them now. This one is called [strong parameters](action_controller_overview.html#strong-parameters), +which requires us to tell Rails exactly which parameters are allowed into our +controller actions. + +Why do you have to bother? The ability to grab and automatically assign all +controller parameters to your model in one shot makes the programmer's job +easier, but this convenience also allows malicious use. What if a request to +the server was crafted to look like a new article form submit but also included +extra fields with values that violated your applications integrity? They would +be 'mass assigned' into your model and then into the database along with the +good stuff - potentially breaking your application or worse. + +We have to whitelist our controller parameters to prevent wrongful mass +assignment. In this case, we want to both allow and require the `title` and +`text` parameters for valid use of `create`. The syntax for this introduces +`require` and `permit`. The change will involve one line in the `create` action: + +```ruby + @article = Article.new(params.require(:article).permit(:title, :text)) +``` + +This is often factored out into its own method so it can be reused by multiple +actions in the same controller, for example `create` and `update`. Above and +beyond mass assignment issues, the method is often made `private` to make sure +it can't be called outside its intended context. Here is the result: ```ruby def create - @post = Post.new(post_params) + @article = Article.new(article_params) - @post.save - redirect_to @post + @article.save + redirect_to @article end private - def post_params - params.require(:post).permit(:title, :text) + def article_params + params.require(:article).permit(:title, :text) end ``` -See the `permit`? It allows us to accept both `title` and `text` in this -action. +TIP: For more information, refer to the reference above and +[this blog article about Strong Parameters] +(http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). -TIP: Note that `def post_params` is private. This new approach prevents an -attacker from setting the model's attributes by manipulating the hash passed to -the model. -For more information, refer to -[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). +### Showing Articles -### Showing Posts - -If you submit the form again now, Rails will complain about not finding -the `show` action. That's not very useful though, so let's add the -`show` action before proceeding. +If you submit the form again now, Rails will complain about not finding the +`show` action. That's not very useful though, so let's add the `show` action +before proceeding. As we have seen in the output of `rake routes`, the route for `show` action is as follows: ``` -post GET /posts/:id(.:format) posts#show +article GET /articles/:id(.:format) articles#show ``` The special syntax `:id` tells rails that this route expects an `:id` -parameter, which in our case will be the id of the post. +parameter, which in our case will be the id of the article. As we did before, we need to add the `show` action in -`app/controllers/posts_controller.rb` and its respective view. +`app/controllers/articles_controller.rb` and its respective view. + +NOTE: A frequent practice is to place the standard CRUD actions in each +controller in the following order: `index`, `show`, `new`, `edit`, `create`, `update` +and `destroy`. You may use any order you choose, but keep in mind that these +are public methods; as mentioned earlier in this guide, they must be placed +before any private or protected method in the controller in order to work. + +Given that, let's add the `show` action, as follows: ```ruby -def show - @post = Post.find(params[:id]) -end +class ArticlesController < ApplicationController + def show + @article = Article.find(params[:id]) + end + + def new + end + + # snipped for brevity ``` -A couple of things to note. We use `Post.find` to find the post we're +A couple of things to note. We use `Article.find` to find the article we're interested in, passing in `params[:id]` to get the `:id` parameter from the -request. We also use an instance variable (prefixed by `@`) to hold a -reference to the post object. We do this because Rails will pass all instance +request. We also use an instance variable (prefixed with `@`) to hold a +reference to the article object. We do this because Rails will pass all instance variables to the view. -Now, create a new file `app/views/posts/show.html.erb` with the following +Now, create a new file `app/views/articles/show.html.erb` with the following content: ```html+erb <p> <strong>Title:</strong> - <%= @post.title %> + <%= @article.title %> </p> <p> <strong>Text:</strong> - <%= @post.text %> + <%= @article.text %> </p> ``` -With this change, you should finally be able to create new posts. -Visit <http://localhost:3000/posts/new> and give it a try! +With this change, you should finally be able to create new articles. +Visit <http://localhost:3000/articles/new> and give it a try! -![Show action for posts](images/getting_started/show_action_for_posts.png) +![Show action for articles](images/getting_started/show_action_for_articles.png) -### Listing all posts +### Listing all articles -We still need a way to list all our posts, so let's do that. +We still need a way to list all our articles, so let's do that. The route for this as per output of `rake routes` is: ``` -posts GET /posts(.:format) posts#index +articles GET /articles(.:format) articles#index ``` -Add the corresponding `index` action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file: +Add the corresponding `index` action for that route inside the +`ArticlesController` in the `app/controllers/articles_controller.rb` file. +When we write an `index` action, the usual practice is to place it as the +first method in the controller. Let's do it: ```ruby -def index - @posts = Post.all -end +class ArticlesController < ApplicationController + def index + @articles = Article.all + end + + def show + @article = Article.find(params[:id]) + end + + def new + end + + # snipped for brevity ``` -And then finally, add view for this action, located at `app/views/posts/index.html.erb`: +And then finally, add the view for this action, located at +`app/views/articles/index.html.erb`: ```html+erb -<h1>Listing posts</h1> +<h1>Listing articles</h1> <table> <tr> @@ -830,75 +904,76 @@ And then finally, add view for this action, located at `app/views/posts/index.ht <th>Text</th> </tr> - <% @posts.each do |post| %> + <% @articles.each do |article| %> <tr> - <td><%= post.title %></td> - <td><%= post.text %></td> + <td><%= article.title %></td> + <td><%= article.text %></td> </tr> <% end %> </table> ``` -Now if you go to `http://localhost:3000/posts` you will see a list of all the -posts that you have created. +Now if you go to <http://localhost:3000/articles> you will see a list of all the +articles that you have created. ### Adding links -You can now create, show, and list posts. Now let's add some links to +You can now create, show, and list articles. Now let's add some links to navigate through pages. Open `app/views/welcome/index.html.erb` and modify it as follows: ```html+erb <h1>Hello, Rails!</h1> -<%= link_to 'My Blog', controller: 'posts' %> +<%= link_to 'My Blog', controller: 'articles' %> ``` The `link_to` method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path -for posts. +for articles. -Let's add links to the other views as well, starting with adding this "New Post" -link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag: +Let's add links to the other views as well, starting with adding this +"New Article" link to `app/views/articles/index.html.erb`, placing it above the +`<table>` tag: ```erb -<%= link_to 'New post', new_post_path %> +<%= link_to 'New article', new_article_path %> ``` -This link will allow you to bring up the form that lets you create a new post. -You should also add a link to this template - `app/views/posts/new.html.erb` - -to go back to the `index` action. Do this by adding this underneath the form in -this template: +This link will allow you to bring up the form that lets you create a new article. + +Now, add another link in `app/views/articles/new.html.erb`, underneath the +form, to go back to the `index` action: ```erb -<%= form_for :post do |f| %> +<%= form_for :article, url: articles_path do |f| %> ... <% end %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -Finally, add another link to the `app/views/posts/show.html.erb` template to go -back to the `index` action as well, so that people who are viewing a single post -can go back and view the whole list again: +Finally, add a link to the `app/views/articles/show.html.erb` template to +go back to the `index` action as well, so that people who are viewing a single +article can go back and view the whole list again: ```html+erb <p> <strong>Title:</strong> - <%= @post.title %> + <%= @article.title %> </p> <p> <strong>Text:</strong> - <%= @post.text %> + <%= @article.text %> </p> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -TIP: If you want to link to an action in the same controller, you don't -need to specify the `:controller` option, as Rails will use the current -controller by default. +TIP: If you want to link to an action in the same controller, you don't need to +specify the `:controller` option, as Rails will use the current controller by +default. TIP: In development mode (which is what you're working in by default), Rails reloads your application with every browser request, so there's no need to stop @@ -906,92 +981,97 @@ and restart the web server when a change is made. ### Adding Some Validation -The model file, `app/models/post.rb` is about as simple as it can get: +The model file, `app/models/article.rb` is about as simple as it can get: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base end ``` -There isn't much to this file - but note that the `Post` class inherits from +There isn't much to this file - but note that the `Article` class inherits from `ActiveRecord::Base`. Active Record supplies a great deal of functionality to your Rails models for free, including basic database CRUD (Create, Read, Update, Destroy) operations, data validation, as well as sophisticated search support and the ability to relate multiple models to one another. Rails includes methods to help you validate the data that you send to models. -Open the `app/models/post.rb` file and edit it: +Open the `app/models/article.rb` file and edit it: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base validates :title, presence: true, length: { minimum: 5 } end ``` -These changes will ensure that all posts have a title that is at least five +These changes will ensure that all articles have a title that is at least five characters long. Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects. Validations are covered in detail in [Active -Record Validations](active_record_validations.html) +Record Validations](active_record_validations.html). -With the validation now in place, when you call `@post.save` on an invalid -post, it will return `false`. If you open `app/controllers/posts_controller.rb` -again, you'll notice that we don't check the result of calling `@post.save` -inside the `create` action. If `@post.save` fails in this situation, we need to -show the form back to the user. To do this, change the `new` and `create` -actions inside `app/controllers/posts_controller.rb` to these: +With the validation now in place, when you call `@article.save` on an invalid +article, it will return `false`. If you open +`app/controllers/articles_controller.rb` again, you'll notice that we don't +check the result of calling `@article.save` inside the `create` action. +If `@article.save` fails in this situation, we need to show the form back to the +user. To do this, change the `new` and `create` actions inside +`app/controllers/articles_controller.rb` to these: ```ruby def new - @post = Post.new + @article = Article.new end def create - @post = Post.new(post_params) + @article = Article.new(article_params) - if @post.save - redirect_to @post + if @article.save + redirect_to @article else render 'new' end end private - def post_params - params.require(:post).permit(:title, :text) + def article_params + params.require(:article).permit(:title, :text) end ``` -The `new` action is now creating a new instance variable called `@post`, and +The `new` action is now creating a new instance variable called `@article`, and you'll see why that is in just a few moments. Notice that inside the `create` action we use `render` instead of `redirect_to` -when `save` returns `false`. The `render` method is used so that the `@post` +when `save` returns `false`. The `render` method is used so that the `@article` object is passed back to the `new` template when it is rendered. This rendering -is done within the same request as the form submission, whereas the `redirect_to` -will tell the browser to issue another request. +is done within the same request as the form submission, whereas the +`redirect_to` will tell the browser to issue another request. If you reload -<http://localhost:3000/posts/new> and -try to save a post without a title, Rails will send you back to the +<http://localhost:3000/articles/new> and +try to save an article without a title, Rails will send you back to the form, but that's not very useful. You need to tell the user that something went wrong. To do that, you'll modify -`app/views/posts/new.html.erb` to check for error messages: +`app/views/articles/new.html.erb` to check for error messages: ```html+erb -<%= form_for :post, url: posts_path do |f| %> - <% if @post.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:</h2> - <ul> - <% @post.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> +<%= form_for :article, url: articles_path do |f| %> + + <% if @article.errors.any? %> + <div id="error_explanation"> + <h2> + <%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved: + </h2> + <ul> + <% @article.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> + <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -1005,64 +1085,85 @@ something went wrong. To do that, you'll modify <p> <%= f.submit %> </p> + <% end %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` A few things are going on. We check if there are any errors with -`@post.errors.any?`, and in that case we show a list of all -errors with `@post.errors.full_messages`. +`@article.errors.any?`, and in that case we show a list of all +errors with `@article.errors.full_messages`. `pluralize` is a rails helper that takes a number and a string as its arguments. If the number is greater than one, the string will be automatically pluralized. -The reason why we added `@post = Post.new` in the `PostsController` is that -otherwise `@post` would be `nil` in our view, and calling -`@post.errors.any?` would throw an error. +The reason why we added `@article = Article.new` in the `ArticlesController` is +that otherwise `@article` would be `nil` in our view, and calling +`@article.errors.any?` would throw an error. TIP: Rails automatically wraps fields that contain an error with a div with class `field_with_errors`. You can define a css rule to make them standout. -Now you'll get a nice error message when saving a post without title when you -attempt to do just that on the new post form [(http://localhost:3000/posts/new)](http://localhost:3000/posts/new). +Now you'll get a nice error message when saving an article without title when +you attempt to do just that on the new article form +<http://localhost:3000/articles/new>: ![Form With Errors](images/getting_started/form_with_errors.png) -### Updating Posts +### Updating Articles We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating -posts. +articles. -The first step we'll take is adding an `edit` action to the `PostsController`. +The first step we'll take is adding an `edit` action to the `ArticlesController`, +generally between the `new` and `create` actions, as shown: ```ruby +def new + @article = Article.new +end + def edit - @post = Post.find(params[:id]) + @article = Article.find(params[:id]) +end + +def create + @article = Article.new(article_params) + + if @article.save + redirect_to @article + else + render 'new' + end end ``` The view will contain a form similar to the one we used when creating -new posts. Create a file called `app/views/posts/edit.html.erb` and make +new articles. Create a file called `app/views/articles/edit.html.erb` and make it look as follows: ```html+erb -<h1>Editing post</h1> - -<%= form_for :post, url: post_path(@post), method: :patch do |f| %> - <% if @post.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:</h2> - <ul> - <% @post.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> +<h1>Editing article</h1> + +<%= form_for :article, url: article_path(@article), method: :patch do |f| %> + + <% if @article.errors.any? %> + <div id="error_explanation"> + <h2> + <%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved: + </h2> + <ul> + <% @article.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> + <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -1076,9 +1177,10 @@ it look as follows: <p> <%= f.submit %> </p> + <% end %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` This time we point the form to the `update` action, which is not defined yet @@ -1088,42 +1190,60 @@ The `method: :patch` 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. -TIP: By default forms built with the _form_for_ helper are sent via `POST`. +The first parameter of `form_for` can be an object, say, `@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. This is what is happening here. +More details can be found in [form_for documentation] +(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for). -Next we need to create the `update` action in `app/controllers/posts_controller.rb`: +Next, we need to create the `update` action in +`app/controllers/articles_controller.rb`. +Add it between the `create` action and the `private` method: ```ruby +def create + @article = Article.new(article_params) + + if @article.save + redirect_to @article + else + render 'new' + end +end + def update - @post = Post.find(params[:id]) + @article = Article.find(params[:id]) - if @post.update(post_params) - redirect_to @post + if @article.update(article_params) + redirect_to @article else render 'edit' end end private - def post_params - params.require(:post).permit(:title, :text) + def article_params + params.require(:article).permit(:title, :text) end ``` The new method, `update`, is used when you want to update a record that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the -post we want to show the form back to the user. +article we want to show the form back to the user. -We reuse the `post_params` method that we defined earlier for the create action. +We reuse the `article_params` method that we defined earlier for the create +action. TIP: You don't need to pass all attributes to `update`. For -example, if you'd call `@post.update(title: 'A new title')` +example, if you'd call `@article.update(title: 'A new title')` Rails would only update the `title` attribute, leaving all other attributes untouched. Finally, we want to show a link to the `edit` action in the list of all the -posts, so let's add that now to `app/views/posts/index.html.erb` to make it -appear next to the "Show" link: +articles, so let's add that now to `app/views/articles/index.html.erb` to make +it appear next to the "Show" link: ```html+erb <table> @@ -1133,26 +1253,26 @@ appear next to the "Show" link: <th colspan="2"></th> </tr> -<% @posts.each do |post| %> - <tr> - <td><%= post.title %></td> - <td><%= post.text %></td> - <td><%= link_to 'Show', post_path(post) %></td> - <td><%= link_to 'Edit', edit_post_path(post) %></td> - </tr> -<% end %> + <% @articles.each do |article| %> + <tr> + <td><%= article.title %></td> + <td><%= article.text %></td> + <td><%= link_to 'Show', article_path(article) %></td> + <td><%= link_to 'Edit', edit_article_path(article) %></td> + </tr> + <% end %> </table> ``` -And we'll also add one to the `app/views/posts/show.html.erb` template as well, -so that there's also an "Edit" link on a post's page. Add this at the bottom of -the template: +And we'll also add one to the `app/views/articles/show.html.erb` template as +well, so that there's also an "Edit" link on an article's page. Add this at the +bottom of the template: ```html+erb ... -<%= link_to 'Back', posts_path %> -| <%= link_to 'Edit', edit_post_path(@post) %> +<%= link_to 'Back', articles_path %> | +<%= link_to 'Edit', edit_article_path(@article) %> ``` And here's how our app looks so far: @@ -1161,30 +1281,34 @@ And here's how our app looks so far: ### Using partials to clean up duplication in views -Our `edit` page looks very similar to the `new` page, in fact they -both share the same code for displaying the form. Let's remove some duplication -by using a view partial. By convention, partial files are prefixed by an -underscore. +Our `edit` page looks very similar to the `new` page; in fact, they +both share the same code for displaying the form. Let's remove this +duplication by using a view partial. By convention, partial files are +prefixed with an underscore. TIP: You can read more about partials in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. -Create a new file `app/views/posts/_form.html.erb` with the following +Create a new file `app/views/articles/_form.html.erb` with the following content: ```html+erb -<%= form_for @post do |f| %> - <% if @post.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:</h2> - <ul> - <% @post.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> +<%= form_for @article do |f| %> + + <% if @article.errors.any? %> + <div id="error_explanation"> + <h2> + <%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved: + </h2> + <ul> + <% @article.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> + <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -1198,46 +1322,47 @@ content: <p> <%= f.submit %> </p> + <% end %> ``` Everything except for the `form_for` declaration remained the same. The reason we can use this shorter, simpler `form_for` declaration -to stand in for either of the other forms is that `@post` is a *resource* +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](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style). +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). -Now, let's update the `app/views/posts/new.html.erb` view to use this new +Now, let's update the `app/views/articles/new.html.erb` view to use this new partial, rewriting it completely: ```html+erb -<h1>New post</h1> +<h1>New article</h1> <%= render 'form' %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -Then do the same for the `app/views/posts/edit.html.erb` view: +Then do the same for the `app/views/articles/edit.html.erb` view: ```html+erb -<h1>Edit post</h1> +<h1>Edit article</h1> <%= render 'form' %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -### Deleting Posts +### Deleting Articles -We're now ready to cover the "D" part of CRUD, deleting posts from the +We're now ready to cover the "D" part of CRUD, deleting articles from the database. Following the REST convention, the route for -deleting posts as per output of `rake routes` is: +deleting articles as per output of `rake routes` is: ```ruby -DELETE /posts/:id(.:format) posts#destroy +DELETE /articles/:id(.:format) articles#destroy ``` The `delete` routing method should be used for routes that destroy @@ -1245,19 +1370,76 @@ resources. If this was left as a typical `get` route, it could be possible for people to craft malicious URLs like this: ```html -<a href='http://example.com/posts/1/destroy'>look at this cat!</a> +<a href='http://example.com/articles/1/destroy'>look at this cat!</a> ``` -We use the `delete` method for destroying resources, and this route is mapped to -the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't -exist yet, but is provided below: +We use the `delete` method for destroying resources, and this route is mapped +to the `destroy` action inside `app/controllers/articles_controller.rb`, which +doesn't exist yet. The `destroy` method is generally the last CRUD action in +the controller, and like the other public CRUD actions, it must be placed +before any `private` or `protected` methods. Let's add it: ```ruby def destroy - @post = Post.find(params[:id]) - @post.destroy + @article = Article.find(params[:id]) + @article.destroy - redirect_to posts_path + redirect_to articles_path +end +``` + +The complete `ArticlesController` in the +`app/controllers/articles_controller.rb` file should now look like this: + +```ruby +class ArticlesController < ApplicationController + def index + @articles = Article.all + end + + def show + @article = Article.find(params[:id]) + end + + def new + @article = Article.new + end + + def edit + @article = Article.find(params[:id]) + end + + def create + @article = Article.new(article_params) + + if @article.save + redirect_to @article + else + render 'new' + end + end + + def update + @article = Article.find(params[:id]) + + if @article.update(article_params) + redirect_to @article + else + render 'edit' + end + end + + def destroy + @article = Article.find(params[:id]) + @article.destroy + + redirect_to articles_path + end + + private + def article_params + params.require(:article).permit(:title, :text) + end end ``` @@ -1266,12 +1448,11 @@ them from the database. Note that we don't need to add a view for this action since we're redirecting to the `index` action. Finally, add a 'Destroy' link to your `index` action template -(`app/views/posts/index.html.erb`) to wrap everything -together. +(`app/views/articles/index.html.erb`) to wrap everything together. ```html+erb -<h1>Listing Posts</h1> -<%= link_to 'New post', new_post_path %> +<h1>Listing Articles</h1> +<%= link_to 'New article', new_article_path %> <table> <tr> <th>Title</th> @@ -1279,16 +1460,17 @@ together. <th colspan="3"></th> </tr> -<% @posts.each do |post| %> - <tr> - <td><%= post.title %></td> - <td><%= post.text %></td> - <td><%= link_to 'Show', post_path(post) %></td> - <td><%= link_to 'Edit', edit_post_path(post) %></td> - <td><%= link_to 'Destroy', post_path(post), - method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> -<% end %> + <% @articles.each do |article| %> + <tr> + <td><%= article.title %></td> + <td><%= article.text %></td> + <td><%= link_to 'Show', article_path(article) %></td> + <td><%= link_to 'Edit', edit_article_path(article) %></td> + <td><%= link_to 'Destroy', article_path(article), + method: :delete, + data: { confirm: 'Are you sure?' } %></td> + </tr> + <% end %> </table> ``` @@ -1304,34 +1486,33 @@ Without this file, the confirmation dialog box wouldn't appear. ![Confirm Dialog](images/getting_started/confirm_dialog.png) Congratulations, you can now create, show, list, update and destroy -posts. +articles. -TIP: In general, Rails encourages the use of resources objects in place -of declaring routes manually. -For more information about routing, see +TIP: In general, Rails encourages using resources objects instead of +declaring routes manually. For more information about routing, see [Rails Routing from the Outside In](routing.html). Adding a Second Model --------------------- It's time to add a second model to the application. The second model will handle -comments on posts. +comments on articles. ### Generating a Model We're going to see the same generator that we used before when creating -the `Post` model. This time we'll create a `Comment` model to hold -reference of post comments. Run this command in your terminal: +the `Article` model. This time we'll create a `Comment` model to hold +reference of article comments. Run this command in your terminal: ```bash -$ rails generate model Comment commenter:string body:text post:references +$ bin/rails generate model Comment commenter:string body:text article:references ``` This command will generate four files: | File | Purpose | | -------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| db/migrate/20100207235629_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) | +| db/migrate/20140120201010_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) | | app/models/comment.rb | The Comment model | | test/models/comment_test.rb | Testing harness for the comments model | | test/fixtures/comments.yml | Sample comments for use in testing | @@ -1340,12 +1521,12 @@ First, take a look at `app/models/comment.rb`: ```ruby class Comment < ActiveRecord::Base - belongs_to :post + belongs_to :article end ``` -This is very similar to the `Post` model that you saw earlier. The difference -is the line `belongs_to :post`, which sets up an Active Record _association_. +This is very similar to the `Article` model that you saw earlier. The difference +is the line `belongs_to :article`, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide. In addition to the model, Rails has also made a migration to create the @@ -1357,10 +1538,13 @@ class CreateComments < ActiveRecord::Migration create_table :comments do |t| t.string :commenter t.text :body - t.references :post, index: true - t.timestamps + # this line adds an integer column called `article_id`. + t.references :article, index: true + + t.timestamps null: false end + add_foreign_key :comments, :articles end end ``` @@ -1370,7 +1554,7 @@ the two models. An index for this association is also created on this column. Go ahead and run the migration: ```bash -$ rake db:migrate +$ bin/rake db:migrate ``` Rails is smart enough to only execute the migrations that have not already been @@ -1380,32 +1564,35 @@ run against the current database, so in this case you will just see: == CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0115s +-- add_foreign_key(:comments, :articles) + -> 0.0000s == CreateComments: migrated (0.0119s) ======================================== ``` ### Associating Models Active Record associations let you easily declare the relationship between two -models. In the case of comments and posts, you could write out the relationships -this way: +models. In the case of comments and articles, you could write out the +relationships this way: -* Each comment belongs to one post. -* One post can have many comments. +* Each comment belongs to one article. +* One article can have many comments. In fact, this is very close to the syntax that Rails uses to declare this association. You've already seen the line of code inside the `Comment` model -(app/models/comment.rb) that makes each comment belong to a Post: +(app/models/comment.rb) that makes each comment belong to an Article: ```ruby class Comment < ActiveRecord::Base - belongs_to :post + belongs_to :article end ``` -You'll need to edit `app/models/post.rb` to add the other side of the association: +You'll need to edit `app/models/article.rb` to add the other side of the +association: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_many :comments validates :title, presence: true, length: { minimum: 5 } @@ -1413,29 +1600,31 @@ end ``` These two declarations enable a good bit of automatic behavior. For example, if -you have an instance variable `@post` containing a post, you can retrieve all -the comments belonging to that post as an array using `@post.comments`. +you have an instance variable `@article` containing an article, you can retrieve +all the comments belonging to that article as an array using +`@article.comments`. TIP: For more information on Active Record associations, see the [Active Record Associations](association_basics.html) guide. ### Adding a Route for Comments -As with the `welcome` controller, we will need to add a route so that Rails knows -where we would like to navigate to see `comments`. Open up the +As with the `welcome` controller, we will need to add a route so that Rails +knows where we would like to navigate to see `comments`. Open up the `config/routes.rb` file again, and edit it as follows: ```ruby -resources :posts do +resources :articles do resources :comments end ``` -This creates `comments` as a _nested resource_ within `posts`. This is another -part of capturing the hierarchical relationship that exists between posts and -comments. +This creates `comments` as a _nested resource_ within `articles`. This is +another part of capturing the hierarchical relationship that exists between +articles and comments. -TIP: For more information on routing, see the [Rails Routing](routing.html) guide. +TIP: For more information on routing, see the [Rails Routing](routing.html) +guide. ### Generating a Controller @@ -1443,10 +1632,10 @@ With the model in hand, you can turn your attention to creating a matching controller. Again, we'll use the same generator we used before: ```bash -$ rails generate controller Comments +$ bin/rails generate controller Comments ``` -This creates six files and one empty directory: +This creates five files and one empty directory: | File/Directory | Purpose | | -------------------------------------------- | ---------------------------------------- | @@ -1454,32 +1643,31 @@ This creates six files and one empty directory: | app/views/comments/ | Views of the controller are stored here | | test/controllers/comments_controller_test.rb | The test for the controller | | app/helpers/comments_helper.rb | A view helper file | -| test/helpers/comments_helper_test.rb | The test for the helper | -| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller | -| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller | +| app/assets/javascripts/comment.coffee | CoffeeScript for the controller | +| app/assets/stylesheets/comment.scss | Cascading style sheet for the controller | Like with any blog, our readers will create their comments directly after -reading the post, and once they have added their comment, will be sent back to -the post show page to see their comment now listed. Due to this, our +reading the article, and once they have added their comment, will be sent back +to the article show page to see their comment now listed. Due to this, our `CommentsController` is there to provide a method to create comments and delete spam comments when they arrive. -So first, we'll wire up the Post show template -(`app/views/posts/show.html.erb`) to let us make a new comment: +So first, we'll wire up the Article show template +(`app/views/articles/show.html.erb`) to let us make a new comment: ```html+erb <p> <strong>Title:</strong> - <%= @post.title %> + <%= @article.title %> </p> <p> <strong>Text:</strong> - <%= @post.text %> + <%= @article.text %> </p> <h2>Add a comment:</h2> -<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %> <p> <%= f.label :commenter %><br> <%= f.text_field :commenter %> @@ -1493,22 +1681,22 @@ So first, we'll wire up the Post show template </p> <% end %> -<%= link_to 'Back', posts_path %> -| <%= link_to 'Edit', edit_post_path(@post) %> +<%= link_to 'Back', articles_path %> | +<%= link_to 'Edit', edit_article_path(@article) %> ``` -This adds a form on the `Post` show page that creates a new comment by +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 -an array, which will build a nested route, such as `/posts/1/comments`. +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`: ```ruby class CommentsController < ApplicationController def create - @post = Post.find(params[:post_id]) - @comment = @post.comments.create(comment_params) - redirect_to post_path(@post) + @article = Article.find(params[:article_id]) + @comment = @article.comments.create(comment_params) + redirect_to article_path(@article) end private @@ -1518,35 +1706,36 @@ class CommentsController < ApplicationController end ``` -You'll see a bit more complexity here than you did in the controller for posts. -That's a side-effect of the nesting that you've set up. Each request for a -comment has to keep track of the post to which the comment is attached, thus the -initial call to the `find` method of the `Post` model to get the post in question. +You'll see a bit more complexity here than you did in the controller for +articles. That's a side-effect of the nesting that you've set up. Each request +for a comment has to keep track of the article to which the comment is attached, +thus the initial call to the `find` method of the `Article` model to get the +article in question. In addition, the code takes advantage of some of the methods available for an -association. We use the `create` method on `@post.comments` to create and save -the comment. This will automatically link the comment so that it belongs to that -particular post. +association. We use the `create` method on `@article.comments` to create and +save the comment. This will automatically link the comment so that it belongs to +that particular article. -Once we have made the new comment, we send the user back to the original post -using the `post_path(@post)` helper. As we have already seen, this calls the -`show` action of the `PostsController` which in turn renders the `show.html.erb` -template. This is where we want the comment to show, so let's add that to the -`app/views/posts/show.html.erb`. +Once we have made the new comment, we send the user back to the original article +using the `article_path(@article)` helper. As we have already seen, this calls +the `show` action of the `ArticlesController` which in turn renders the +`show.html.erb` template. This is where we want the comment to show, so let's +add that to the `app/views/articles/show.html.erb`. ```html+erb <p> <strong>Title:</strong> - <%= @post.title %> + <%= @article.title %> </p> <p> <strong>Text:</strong> - <%= @post.text %> + <%= @article.text %> </p> <h2>Comments</h2> -<% @post.comments.each do |comment| %> +<% @article.comments.each do |comment| %> <p> <strong>Commenter:</strong> <%= comment.commenter %> @@ -1559,7 +1748,7 @@ template. This is where we want the comment to show, so let's add that to the <% end %> <h2>Add a comment:</h2> -<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %> <p> <%= f.label :commenter %><br> <%= f.text_field :commenter %> @@ -1573,26 +1762,26 @@ template. This is where we want the comment to show, so let's add that to the </p> <% end %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> ``` -Now you can add posts and comments to your blog and have them show up in the +Now you can add articles and comments to your blog and have them show up in the right places. -![Post with Comments](images/getting_started/post_with_comments.png) +![Article with Comments](images/getting_started/article_with_comments.png) Refactoring ----------- -Now that we have posts and comments working, take a look at the -`app/views/posts/show.html.erb` template. It is getting long and awkward. We can -use partials to clean it up. +Now that we have articles and comments working, take a look at the +`app/views/articles/show.html.erb` template. It is getting long and awkward. We +can use partials to clean it up. ### Rendering Partial Collections -First, we will make a comment partial to extract showing all the comments for the -post. Create the file `app/views/comments/_comment.html.erb` and put the +First, we will make a comment partial to extract showing all the comments for +the article. Create the file `app/views/comments/_comment.html.erb` and put the following into it: ```html+erb @@ -1607,25 +1796,25 @@ following into it: </p> ``` -Then you can change `app/views/posts/show.html.erb` to look like the +Then you can change `app/views/articles/show.html.erb` to look like the following: ```html+erb <p> <strong>Title:</strong> - <%= @post.title %> + <%= @article.title %> </p> <p> <strong>Text:</strong> - <%= @post.text %> + <%= @article.text %> </p> <h2>Comments</h2> -<%= render @post.comments %> +<%= render @article.comments %> <h2>Add a comment:</h2> -<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %> <p> <%= f.label :commenter %><br> <%= f.text_field :commenter %> @@ -1639,13 +1828,13 @@ following: </p> <% end %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> ``` This will now render the partial in `app/views/comments/_comment.html.erb` once -for each comment that is in the `@post.comments` collection. As the `render` -method iterates over the `@post.comments` collection, it assigns each +for each comment that is in the `@article.comments` collection. As the `render` +method iterates over the `@article.comments` collection, it assigns each comment to a local variable named the same as the partial, in this case `comment` which is then available in the partial for us to show. @@ -1655,7 +1844,7 @@ 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([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %> <p> <%= f.label :commenter %><br> <%= f.text_field :commenter %> @@ -1670,27 +1859,27 @@ create a file `app/views/comments/_form.html.erb` containing: <% end %> ``` -Then you make the `app/views/posts/show.html.erb` look like the following: +Then you make the `app/views/articles/show.html.erb` look like the following: ```html+erb <p> <strong>Title:</strong> - <%= @post.title %> + <%= @article.title %> </p> <p> <strong>Text:</strong> - <%= @post.text %> + <%= @article.text %> </p> <h2>Comments</h2> -<%= render @post.comments %> +<%= render @article.comments %> <h2>Add a comment:</h2> -<%= render "comments/form" %> +<%= render 'comments/form' %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> ``` The second render just defines the partial template we want to render, @@ -1698,8 +1887,8 @@ The second render just defines the partial template we want to render, string and realize that you want to render the `_form.html.erb` file in the `app/views/comments` directory. -The `@post` object is available to any partials rendered in the view because we -defined it as an instance variable. +The `@article` object is available to any partials rendered in the view because +we defined it as an instance variable. Deleting Comments ----------------- @@ -1723,30 +1912,30 @@ So first, let's add the delete link in the </p> <p> - <%= link_to 'Destroy Comment', [comment.post, comment], + <%= link_to 'Destroy Comment', [comment.article, comment], method: :delete, data: { confirm: 'Are you sure?' } %> </p> ``` Clicking this new "Destroy Comment" link will fire off a `DELETE -/posts/:post_id/comments/:id` to our `CommentsController`, which can then use -this to find the comment we want to delete, so let's add a `destroy` action to our -controller (`app/controllers/comments_controller.rb`): +/articles/:article_id/comments/:id` to our `CommentsController`, which can then +use this to find the comment we want to delete, so let's add a `destroy` action +to our controller (`app/controllers/comments_controller.rb`): ```ruby class CommentsController < ApplicationController def create - @post = Post.find(params[:post_id]) - @comment = @post.comments.create(comment_params) - redirect_to post_path(@post) + @article = Article.find(params[:article_id]) + @comment = @article.comments.create(comment_params) + redirect_to article_path(@article) end def destroy - @post = Post.find(params[:post_id]) - @comment = @post.comments.find(params[:id]) + @article = Article.find(params[:article_id]) + @comment = @article.comments.find(params[:id]) @comment.destroy - redirect_to post_path(@post) + redirect_to article_path(@article) end private @@ -1756,20 +1945,20 @@ class CommentsController < ApplicationController end ``` -The `destroy` action will find the post we are looking at, locate the comment -within the `@post.comments` collection, and then remove it from the -database and send us back to the show action for the post. +The `destroy` action will find the article we are looking at, locate the comment +within the `@article.comments` collection, and then remove it from the +database and send us back to the show action for the article. ### Deleting Associated Objects -If you delete a post then its associated comments will also need to be deleted. -Otherwise they would simply occupy space in the database. Rails allows you to -use the `dependent` option of an association to achieve this. Modify the Post -model, `app/models/post.rb`, as follows: +If you delete an article, its associated comments will also need to be +deleted, otherwise they would simply occupy space in the database. Rails allows +you to use the `dependent` option of an association to achieve this. Modify the +Article model, `app/models/article.rb`, as follows: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } @@ -1781,34 +1970,35 @@ Security ### Basic Authentication -If you were to publish your blog online, anybody would be able to add, edit and -delete posts or delete comments. +If you were to publish your blog online, anyone would be able to add, edit and +delete articles or delete comments. Rails provides a very simple HTTP authentication system that will work nicely in this situation. -In the `PostsController` we need to have a way to block access to the various -actions if the person is not authenticated, here we can use the Rails -`http_basic_authenticate_with` method, allowing access to the requested +In the `ArticlesController` we need to have a way to block access to the +various actions if the person is not authenticated. Here we can use the Rails +`http_basic_authenticate_with` method, which allows access to the requested action if that method allows it. To use the authentication system, we specify it at the top of our -`PostsController`, in this case, we want the user to be authenticated on every -action, except for `index` and `show`, so we write that in `app/controllers/posts_controller.rb`: +`ArticlesController` in `app/controllers/articles_controller.rb`. In our case, +we want the user to be authenticated on every action except `index` and `show`, +so we write that: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] def index - @posts = Post.all + @articles = Article.all end # snipped for brevity ``` -We also only want to allow authenticated users to delete comments, so in the +We also want to allow only authenticated users to delete comments, so in the `CommentsController` (`app/controllers/comments_controller.rb`) we write: ```ruby @@ -1817,21 +2007,22 @@ class CommentsController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy def create - @post = Post.find(params[:post_id]) - ... + @article = Article.find(params[:article_id]) + # ... end # snipped for brevity ``` -Now if you try to create a new post, you will be greeted with a basic HTTP -Authentication challenge +Now if you try to create a new article, you will be greeted with a basic HTTP +Authentication challenge: ![Basic HTTP Authentication Challenge](images/getting_started/challenge.png) Other authentication methods are available for Rails applications. Two popular -authentication add-ons for Rails are the [Devise](https://github.com/plataformatec/devise) -rails engine and the [Authlogic](https://github.com/binarylogic/authlogic) gem, +authentication add-ons for Rails are the +[Devise](https://github.com/plataformatec/devise) rails engine and +the [Authlogic](https://github.com/binarylogic/authlogic) gem, along with a number of others. @@ -1839,18 +2030,20 @@ along with a number of others. Security, especially in web applications, is a broad and detailed area. Security in your Rails application is covered in more depth in -The [Ruby on Rails Security Guide](security.html) +the [Ruby on Rails Security Guide](security.html). What's Next? ------------ Now that you've seen your first Rails application, you should feel free to -update it and experiment on your own. But you don't have to do everything -without help. As you need assistance getting up and running with Rails, feel -free to consult these support resources: +update it and experiment on your own. + +Remember you don't have to do everything without help. As you need assistance +getting up and running with Rails, feel free to consult these support +resources: -* The [Ruby on Rails guides](index.html) +* The [Ruby on Rails Guides](index.html) * The [Ruby on Rails Tutorial](http://railstutorial.org/book) * The [Ruby on Rails mailing list](http://groups.google.com/group/rubyonrails-talk) * The [#rubyonrails](irc://irc.freenode.net/#rubyonrails) channel on irc.freenode.net @@ -1866,7 +2059,7 @@ command-line utility: in your web browser to explore the API documentation. TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake -task you need to install the RedCloth gem. Add it to your `Gemfile` and run +task you need to install the Redcarpet and Nokogiri gems. Add it to your `Gemfile` and run `bundle install` and you're ready to go. Configuration Gotchas @@ -1887,15 +2080,16 @@ cannot be automatically detected by Rails and corrected. Two very common sources of data that are not UTF-8: -* Your text editor: Most text editors (such as TextMate), default to saving files as - UTF-8. If your text editor does not, this can result in special characters that you - enter in your templates (such as é) to appear as a diamond with a question mark inside - in the browser. This also applies to your i18n translation files. - Most editors that do not already default to UTF-8 (such as some versions of - Dreamweaver) offer a way to change the default to UTF-8. Do so. -* Your database: Rails defaults to converting data from your database into UTF-8 at - the boundary. However, if your database is not using UTF-8 internally, it may not - be able to store all characters that your users enter. For instance, if your database - is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese - character, the data will be lost forever once it enters the database. If possible, - use UTF-8 as the internal storage of your database. +* Your text editor: Most text editors (such as TextMate), default to saving + files as UTF-8. If your text editor does not, this can result in special + characters that you enter in your templates (such as é) to appear as a diamond + with a question mark inside in the browser. This also applies to your i18n + translation files. Most editors that do not already default to UTF-8 (such as + some versions of Dreamweaver) offer a way to change the default to UTF-8. Do + so. +* Your database: Rails defaults to converting data from your database into UTF-8 + at the boundary. However, if your database is not using UTF-8 internally, it + may not be able to store all characters that your users enter. For instance, + if your database is using Latin-1 internally, and your user enters a Russian, + Hebrew, or Japanese character, the data will be lost forever once it enters + the database. If possible, use UTF-8 as the internal storage of your database. diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 8dfb17a681..779526d733 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Internationalization (I18n) API ===================================== @@ -28,7 +30,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Ruby [I18n Wiki](http://ruby-i18n.org/wiki) for more information. +NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, also use various gems available to add additional functionality or features. See the [rails-i18n gem](https://github.com/svenfuchs/rails-i18n) for more information. How I18n in Ruby on Rails Works ------------------------------- @@ -92,7 +94,7 @@ Rails adds all `.rb` and `.yml` files from the `config/locales` directory to you The default `en.yml` locale in this directory contains a sample pair of translation strings: -```ruby +```yaml en: hello: "Hello world" ``` @@ -101,13 +103,13 @@ 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 you don't set a different locale, `: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. Various [Rails I18n plugins](http://rails-i18n.org/wiki) 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](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. The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced. -The default `application.rb` files has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. +The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. ```ruby # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. @@ -137,7 +139,7 @@ If you want to translate your Rails application to a **single language other tha However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests. -WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, 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 [<em>RESTful</em>](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*](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. The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this: @@ -179,7 +181,7 @@ end # in your /etc/hosts file to try this out locally def extract_locale_from_tld parsed_locale = request.host.split('.').last - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end ``` @@ -192,7 +194,7 @@ We can also set the locale from the _subdomain_ in a very similar way: # in your /etc/hosts file to try this out locally def extract_locale_from_subdomain parsed_locale = request.subdomains.first - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end ``` @@ -212,17 +214,16 @@ The most usual way of setting (and passing) the locale would be to include it in This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though. -Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(locale: I18n.locale))`) would be tedious and probably impossible, of course. +Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(locale: I18n.locale))`, would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our `ApplicationController` then: ```ruby # app/controllers/application_controller.rb -def default_url_options(options={}) - logger.debug "default_url_options is passed options: #{options.inspect}\n" - { locale: I18n.locale } +def default_url_options(options = {}) + { locale: I18n.locale }.merge options end ``` @@ -263,7 +264,7 @@ get '/:locale' => 'dashboard#index' Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.) -NOTE: Have a look at two plugins which simplify work with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master) and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master). +NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator). ### Setting the Locale from the Client Supplied Information @@ -289,7 +290,7 @@ private end ``` -Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). +Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). #### Using GeoIP (or Similar) Database @@ -310,7 +311,7 @@ You most probably have something like this in one of your applications: ```ruby # config/routes.rb -Yourapp::Application.routes.draw do +Rails.application.routes.draw do root to: "home#index" end ``` @@ -370,7 +371,7 @@ NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do So let's add the missing translations into the dictionary files (i.e. do the "localization" part): -```ruby +```yaml # config/locales/en.yml en: hello_world: Hello world! @@ -422,7 +423,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali And in our pirate translations file let's add a time format (it's already there in Rails' defaults for English): -```ruby +```yaml # config/locales/pirate.yml pirate: time: @@ -438,11 +439,11 @@ TIP: Right now you might need to add some more date/time formats in order to mak ### Inflection Rules For Other Locales -Rails 4.0 allows you to define inflection rules (such as rules for singularization and pluralization) for locales other than English. In `config/initializers/inflections.rb`, you can define these rules for multiple locales. The initializer contains a default example for specifying additional rules for English; follow that format for other locales as you see fit. +Rails allows you to define inflection rules (such as rules for singularization and pluralization) for locales other than English. In `config/initializers/inflections.rb`, you can define these rules for multiple locales. The initializer contains a default example for specifying additional rules for English; follow that format for other locales as you see fit. ### Localized Views -Rails 2.3 introduces another convenient localization feature: localized views (templates). Let's say you have a _BooksController_ in your application. Your _index_ action renders content in `app/views/books/index.html.erb` template. When you put a _localized variant_ of this template: `index.es.html.erb` in the same directory, Rails will render content in this template, when the locale is set to `:es`. When the locale is set to the default locale, the generic `index.html.erb` view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in `public`, etc.) +Let's say you have a _BooksController_ in your application. Your _index_ action renders content in `app/views/books/index.html.erb` template. When you put a _localized variant_ of this template: `index.es.html.erb` in the same directory, Rails will render content in this template, when the locale is set to `:es`. When the locale is set to the default locale, the generic `index.html.erb` view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in `public`, etc.) You can make use of this feature, e.g. when working with a large amount of static content, which would be clumsy to put inside YAML or Ruby dictionaries. Bear in mind, though, that any change you would like to do later to the template must be propagated to all of them. @@ -485,8 +486,6 @@ NOTE: The default locale loading mechanism in Rails does not load locale files i ``` -Do check the [Rails i18n Wiki](http://rails-i18n.org/wiki) for list of tools available for managing translations. - Overview of the I18n API Features --------------------------------- @@ -629,7 +628,7 @@ entry[count == 1 ? 0 : 1] I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero). -If the lookup for the key does not return a Hash suitable for pluralization, an `18n::InvalidPluralizationData` exception is raised. +If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised. ### Setting and Passing a Locale @@ -677,58 +676,25 @@ en: <div><%= t('title.html') %></div> ``` -NOTE: Automatic conversion to HTML safe translate text is only available from the `translate` view helper method. - -![i18n demo html safe](images/i18n/demo_html_safe.png) - -How to Store your Custom Translations -------------------------------------- - -The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] - -For example a Ruby Hash providing translations can look like this: - -```ruby -{ - pt: { - foo: { - bar: "baz" - } - } -} -``` - -The equivalent YAML file would look like this: +Interpolation escapes as needed though. For example, given: -```ruby -pt: - foo: - bar: baz +```yaml +en: + welcome_html: "<b>Welcome %{username}!</b>" ``` -As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". - -Here is a "real" example from the Active Support `en.yml` translations YAML file: +you can safely pass the username as set by the user: -```ruby -en: - date: - formats: - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" +```erb +<%# This is safe, it is going to be escaped if needed. %> +<%= t('welcome_html', username: @current_user.username %> ``` -So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: +Safe strings on the other hand are interpolated verbatim. -```ruby -I18n.t 'date.formats.short' -I18n.t 'formats.short', scope: :date -I18n.t :short, scope: 'date.formats' -I18n.t :short, scope: [:date, :formats] -``` +NOTE: Automatic conversion to HTML safe translate text is only available from the `translate` view helper method. -Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. +![i18n demo html safe](images/i18n/demo_html_safe.png) ### Translations for Active Record Models @@ -736,7 +702,7 @@ You can use the methods `Model.model_name.human` and `Model.human_attribute_name For example when you add the following translations: -```ruby +```yaml en: activerecord: models: @@ -751,7 +717,7 @@ Then `User.model_name.human` will return "Dude" and `User.human_attribute_name(" You can also set a plural form for model names, adding as following: -```ruby +```yaml en: activerecord: models: @@ -762,6 +728,19 @@ en: Then `User.model_name.human(count: 2)` will return "Dudes". With `count: 1` or without params will return "Dude". +In the event you need to access nested attributes within a given model, you should nest these under `model/attribute` at the model level of your translation file: + +```yaml +en: + activerecord: + attributes: + user/gender: + female: "Female" + male: "Male" +``` + +Then `User.human_attribute_name("gender.female")` will return "Female". + #### Error Message Scopes Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account. @@ -897,6 +876,24 @@ en: subject: "Welcome to Rails Guides!" ``` +To send parameters to interpolation use the `default_i18n_subject` method on the mailer. + +```ruby +# user_mailer.rb +class UserMailer < ActionMailer::Base + def welcome(user) + mail(to: user.email, subject: default_i18n_subject(user: user.name)) + end +end +``` + +```yaml +en: + user_mailer: + welcome: + subject: "%{user}, welcome to Rails Guides!" +``` + ### Overview of Other Built-In Methods that Provide I18n Support Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview. @@ -921,6 +918,55 @@ Rails uses fixed strings and other localizations, such as format strings and oth * `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope. +How to Store your Custom Translations +------------------------------------- + +The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] + +For example a Ruby Hash providing translations can look like this: + +```yaml +{ + pt: { + foo: { + bar: "baz" + } + } +} +``` + +The equivalent YAML file would look like this: + +```yaml +pt: + foo: + bar: baz +``` + +As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". + +Here is a "real" example from the Active Support `en.yml` translations YAML file: + +```yaml +en: + date: + formats: + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" +``` + +So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: + +```ruby +I18n.t 'date.formats.short' +I18n.t 'formats.short', scope: :date +I18n.t :short, scope: 'date.formats' +I18n.t :short, scope: [:date, :formats] +``` + +Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. + Customize your I18n Setup ------------------------- @@ -1006,9 +1052,9 @@ If you find anything missing or wrong in this guide, please file a ticket on our 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 plugins and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. +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 plugins 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](http://groups.google.com/group/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://github.com/guides/pull-requests). @@ -1016,7 +1062,6 @@ If you find your own locale (language) missing from our [example translations da Resources --------- -* [rails-i18n.org](http://rails-i18n.org) - Homepage of the rails-i18n project. You can find lots of useful resources on the [wiki](http://rails-i18n.org/wiki). * [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. * [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository 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/tree/master) - Code repository for the i18n gem. diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb index 57c224c165..2fdf18a2e9 100644 --- a/guides/source/index.html.erb +++ b/guides/source/index.html.erb @@ -9,6 +9,7 @@ Ruby on Rails Guides <% content_for :index_section do %> <div id="subCol"> <dl> + <dt></dt> <dd class="kindle">Rails Guides are also available for <%= link_to 'Kindle', @mobi %>.</dd> <dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd> </dl> diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 5e2e0ad3e3..8fbb234698 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Rails Initialization Process ================================ @@ -98,9 +100,9 @@ configure the load path for your Gemfile's dependencies. A standard Rails application depends on several gems, specifically: -* abstract * actionmailer * actionpack +* actionview * activemodel * activerecord * activesupport @@ -111,7 +113,6 @@ A standard Rails application depends on several gems, specifically: * i18n * mail * mime-types -* polyglot * rack * rack-cache * rack-mount @@ -119,9 +120,8 @@ A standard Rails application depends on several gems, specifically: * rails * railties * rake -* sqlite3-ruby +* sqlite3 * thor -* treetop * tzinfo ### `rails/commands.rb` @@ -166,6 +166,7 @@ is called. COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) def run_command!(command) + command = parse_command(command) if COMMAND_WHITELIST.include?(command) send(command) else @@ -178,8 +179,7 @@ With the `server` command, Rails will further run the following code: ```ruby def set_application_directory! - Dir.chdir(File.expand_path('../../', APP_PATH)) unless - File.exist?(File.expand_path("config.ru")) + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) end def server @@ -187,6 +187,8 @@ def server require_command!("server") Rails::Server.new.tap do |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. require APP_PATH Dir.chdir(Rails.application.root) server.start @@ -207,6 +209,7 @@ sets up the `Rails::Server` class. require 'fileutils' require 'optparse' require 'action_dispatch' +require 'rails' module Rails class Server < ::Rack::Server @@ -270,10 +273,10 @@ def parse_options(args) options = default_options # Don't evaluate CGI ISINDEX parameters. - # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html + # http://www.meb.uni-bonn.de/docs/cgi/cl.html args.clear if ENV.include?("REQUEST_METHOD") - options.merge! opt_parser.parse! args + options.merge! opt_parser.parse!(args) options[:config] = ::File.expand_path(options[:config]) ENV["RACK_ENV"] = options[:environment] options @@ -284,18 +287,21 @@ With the `default_options` set to this: ```ruby def default_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + { - environment: ENV['RACK_ENV'] || "development", - pid: nil, - Port: 9292, - Host: "0.0.0.0", - AccessLog: [], - config: "config.ru" + :environment => environment, + :pid => nil, + :Port => 9292, + :Host => default_host, + :AccessLog => [], + :config => "config.ru" } end ``` -There is no `REQUEST_METHOD` key in `ENV` so we can skip over that line. The next line merges in the options from `opt_parser` which is defined plainly in `Rack::Server` +There is no `REQUEST_METHOD` key in `ENV` so we can skip over that line. The next line merges in the options from `opt_parser` which is defined plainly in `Rack::Server`: ```ruby def opt_parser @@ -348,11 +354,12 @@ private def print_boot_information ... puts "=> Run `rails server -h` for more startup options" + ... puts "=> Ctrl-C to shutdown server" unless options[:daemonize] end def create_tmp_directories - %w(cache pids sessions sockets).each do |dir_to_make| + %w(cache pids sockets).each do |dir_to_make| FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) end end @@ -368,13 +375,12 @@ private end ``` -This is where the first output of the Rails initialization happens. This -method creates a trap for `INT` signals, so if you `CTRL-C` the server, -it will exit the process. As we can see from the code here, it will -create the `tmp/cache`, `tmp/pids`, `tmp/sessions` and `tmp/sockets` -directories. It then calls `wrapped_app` which is responsible for -creating the Rack app, before creating and assigning an -instance of `ActiveSupport::Logger`. +This is where the first output of the Rails initialization happens. This method +creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the +process. As we can see from the code here, it will create the `tmp/cache`, +`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is +responsible for creating the Rack app, before creating and assigning an instance +of `ActiveSupport::Logger`. The `super` method will call `Rack::Server.start` which begins its definition like this: @@ -434,7 +440,11 @@ The `app` method here is defined like so: ```ruby def app - @app ||= begin + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config +end +... +private + def build_app_and_options_from_config if !::File.exist? options[:config] abort "configuration #{options[:config]} not found" end @@ -443,7 +453,10 @@ def app self.options.merge! options app end -end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end ``` The `options[:config]` value defaults to `config.ru` which contains this: @@ -459,8 +472,14 @@ run <%= app_const %> The `Rack::Builder.parse_file` method here takes the content from this `config.ru` file and parses it using this code: ```ruby -app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", - TOPLEVEL_BINDING, config +app = new_from_string cfgfile, config + +... + +def self.new_from_string(builder_script, file="(rackup)") + eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app", + TOPLEVEL_BINDING, file, 0 +end ``` The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run: @@ -473,11 +492,22 @@ require ::File.expand_path('../config/environment', __FILE__) This file is the common file required by `config.ru` (`rails server`) and Passenger. This is where these two ways to run the server meet; everything before this point has been Rack and Rails setup. -This file begins with requiring `config/application.rb`. +This file begins with requiring `config/application.rb`: + +```ruby +require File.expand_path('../application', __FILE__) +``` ### `config/application.rb` -This file requires `config/boot.rb`, but only if it hasn't been required before, which would be the case in `rails server` but **wouldn't** be the case with Passenger. +This file requires `config/boot.rb`: + +```ruby +require File.expand_path('../boot', __FILE__) +``` + +But only if it hasn't been required before, which would be the case in `rails server` +but **wouldn't** be the case with Passenger. Then the fun begins! @@ -498,11 +528,12 @@ This file is responsible for requiring all the individual frameworks of Rails: require "rails" %w( - active_record - action_controller - action_mailer - rails/test_unit - sprockets + active_record + action_controller + action_view + action_mailer + rails/test_unit + sprockets ).each do |framework| begin require "#{framework}/railtie" @@ -522,12 +553,12 @@ I18n and Rails configuration are all being defined here. ### Back to `config/environment.rb` The rest of `config/application.rb` defines the configuration for the -`Rails::Application` which will be used once the application is fully +`Rails::Application` which will be used once the application is fully initialized. When `config/application.rb` has finished loading Rails and defined the application namespace, we go back to `config/environment.rb`, where the application is initialized. For example, if the application was called -`Blog`, here we would find `Blog::Application.initialize!`, which is -defined in `rails/application.rb` +`Blog`, here we would find `Rails.application.initialize!`, which is +defined in `rails/application.rb`. ### `railties/lib/rails/application.rb` @@ -543,7 +574,7 @@ end ``` As you can see, you can only initialize an app once. The initializers are run through -the `run_initializers` method which is defined in `railties/lib/rails/initializable.rb` +the `run_initializers` method which is defined in `railties/lib/rails/initializable.rb`: ```ruby def run_initializers(group=:default, *args) @@ -555,7 +586,7 @@ def run_initializers(group=:default, *args) end ``` -The run_initializers code itself is tricky. What Rails is doing here is +The `run_initializers` code itself is tricky. What Rails is doing here is traversing all the class ancestors looking for those that respond to an `initializers` method. It then sorts the ancestors by name, and runs them. For example, the `Engine` class will make all the engines available by @@ -568,7 +599,7 @@ initializers (like building the middleware stack) are run last. The `railtie` initializers are the initializers which have been defined on the `Rails::Application` itself and are run between the `bootstrap` and `finishers`. -After this is done we go back to `Rack::Server` +After this is done we go back to `Rack::Server`. ### Rack: lib/rack/server.rb @@ -576,7 +607,11 @@ Last time we left when the `app` method was being defined: ```ruby def app - @app ||= begin + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config +end +... +private + def build_app_and_options_from_config if !::File.exist? options[:config] abort "configuration #{options[:config]} not found" end @@ -585,7 +620,10 @@ def app self.options.merge! options app end -end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end ``` At this point `app` is the Rails app itself (a middleware), and what @@ -603,7 +641,7 @@ def build_app(app) end ``` -Remember, `build_app` was called (by wrapped_app) in the last line of `Server#start`. +Remember, `build_app` was called (by `wrapped_app`) in the last line of `Server#start`. Here's how it looked like when we left: ```ruby @@ -611,40 +649,50 @@ server.run wrapped_app, options, &blk ``` At this point, the implementation of `server.run` will depend on the -server you're using. For example, if you were using Mongrel, here's what +server you're using. For example, if you were using Puma, here's what the `run` method would look like: ```ruby -def self.run(app, options={}) - server = ::Mongrel::HttpServer.new( - options[:Host] || '0.0.0.0', - options[:Port] || 8080, - options[:num_processors] || 950, - options[:throttle] || 0, - options[:timeout] || 60) - # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. - # Use is similar to #run, replacing the app argument with a hash of - # { path=>app, ... } or an instance of Rack::URLMap. - if options[:map] - if app.is_a? Hash - app.each do |path, appl| - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - elsif app.is_a? URLMap - app.instance_variable_get(:@mapping).each do |(host, path, appl)| - next if !host.nil? && !options[:Host].nil? && options[:Host] != host - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - else - raise ArgumentError, "first argument should be a Hash or URLMap" - end - else - server.register('/', Rack::Handler::Mongrel.new(app)) +... +DEFAULT_OPTIONS = { + :Host => '0.0.0.0', + :Port => 8080, + :Threads => '0:16', + :Verbose => false +} + +def self.run(app, options = {}) + options = DEFAULT_OPTIONS.merge(options) + + if options[:Verbose] + app = Rack::CommonLogger.new(app, STDOUT) end + + if options[:environment] + ENV['RACK_ENV'] = options[:environment].to_s + end + + server = ::Puma::Server.new(app) + min, max = options[:Threads].split(':', 2) + + puts "Puma #{::Puma::Const::PUMA_VERSION} starting..." + puts "* Min threads: #{min}, max threads: #{max}" + puts "* Environment: #{ENV['RACK_ENV']}" + puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}" + + server.add_tcp_listener options[:Host], options[:Port] + server.min_threads = min + server.max_threads = max yield server if block_given? - server.run.join + + begin + server.run.join + rescue Interrupt + puts "* Gracefully stopping, waiting for requests to finish" + server.stop(true) + puts "* Goodbye!" + end + end ``` diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 0513066f5a..1005057ca9 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -1,10 +1,9 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> -<meta name="viewport" content="width=device-width, initial-scale=1"> +<meta name="viewport" content="width=device-width, initial-scale=1"/> <title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title> <link rel="stylesheet" type="text/css" href="stylesheets/style.css" /> @@ -36,7 +35,6 @@ <li class="more-info"><a href="https://github.com/rails/rails">Code</a></li> <li class="more-info"><a href="http://rubyonrails.org/screencasts">Screencasts</a></li> <li class="more-info"><a href="http://rubyonrails.org/documentation">Documentation</a></li> - <li class="more-info"><a href="http://rubyonrails.org/ecosystem">Ecosystem</a></li> <li class="more-info"><a href="http://rubyonrails.org/community">Community</a></li> <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li> </ul> @@ -78,7 +76,6 @@ </select> </li> </ul> - </div> </div> </div> <hr class="hide" /> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index c72b584ed6..eb3c188d38 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Layouts and Rendering in Rails ============================== @@ -189,7 +191,7 @@ render file: "/u/apps/warehouse_app/current/app/views/products/show" The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. -NOTE: By default, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the `layout: true` option. +NOTE: By default, the file is rendered using the current layout. TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames. @@ -236,15 +238,35 @@ render inline: "xml.p {'Horrid coding practice!'}", type: :builder #### Rendering Text -You can send plain text - with no markup at all - back to the browser by using the `:text` option to `render`: +You can send plain text - with no markup at all - back to the browser by using +the `:plain` option to `render`: ```ruby -render text: "OK" +render plain: "OK" ``` -TIP: Rendering pure text is most useful when you're responding to Ajax or web service requests that are expecting something other than proper HTML. +TIP: Rendering pure text is most useful when you're responding to Ajax or web +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` +extension for the layout file. + +#### Rendering HTML -NOTE: By default, if you use the `:text` 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. +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 +``` + +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: This option will escape HTML entities if the string is not HTML safe. #### Rendering JSON @@ -276,6 +298,22 @@ render js: "alert('Hello Rails');" This will send the supplied string to the browser with a MIME type of `text/javascript`. +#### Rendering raw body + +You can send a raw content back to the browser, without setting any content +type, by using the `:body` option to `render`: + +```ruby +render body: "raw" +``` + +TIP: This option should be used only if you don't care about the content type of +the response. Using `:plain` or `:html` might be more appropriate in 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. + #### Options for `render` Calls to the `render` method generally accept four options: @@ -389,6 +427,9 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 510 | :not_extended | | | 511 | :network_authentication_required | +NOTE: If you try to render content along with a non-content status code +(100-199, 204, 205 or 304), it will be dropped from the response. + #### 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. @@ -471,33 +512,33 @@ Layout declarations cascade downward in the hierarchy, and more specific layout end ``` -* `posts_controller.rb` +* `articles_controller.rb` ```ruby - class PostsController < ApplicationController + class ArticlesController < ApplicationController end ``` -* `special_posts_controller.rb` +* `special_articles_controller.rb` ```ruby - class SpecialPostsController < PostsController + class SpecialArticlesController < ArticlesController layout "special" end ``` -* `old_posts_controller.rb` +* `old_articles_controller.rb` ```ruby - class OldPostsController < SpecialPostsController + class OldArticlesController < SpecialArticlesController layout false def show - @post = Post.find(params[:id]) + @article = Article.find(params[:id]) end def index - @old_posts = Post.older + @old_articles = Article.older render layout: "old" end # ... @@ -507,10 +548,10 @@ Layout declarations cascade downward in the hierarchy, and more specific layout In this application: * In general, views will be rendered in the `main` layout -* `PostsController#index` will use the `main` layout -* `SpecialPostsController#index` will use the `special` layout -* `OldPostsController#show` will use no layout at all -* `OldPostsController#index` will use the `old` layout +* `ArticlesController#index` will use the `main` layout +* `SpecialArticlesController#index` will use the `special` layout +* `OldArticlesController#show` will use no layout at all +* `OldArticlesController#index` will use the `old` layout #### Avoiding Double Render Errors @@ -868,7 +909,10 @@ You can also specify multiple videos to play by passing an array of videos to th This will produce: ```erb -<video><source src="trailer.ogg" /><source src="movie.ogg" /></video> +<video> + <source src="/videos/trailer.ogg"> + <source src="/videos/movie.ogg"> +</video> ``` #### Linking to Audio Files with the `audio_tag` @@ -986,6 +1030,42 @@ One way to use partials is to treat them as the equivalent of subroutines: as a Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. +As you already could see from the previous sections of this guide, `yield` is a very powerful tool for cleaning up your layouts. Keep in mind that it's pure ruby, so you can use it almost everywhere. For example, we can use it to DRY form layout definition for several similar resources: + +* `users/index.html.erb` + + ```html+erb + <%= render "shared/search_filters", search: @q do |f| %> + <p> + Name contains: <%= f.text_field :name_contains %> + </p> + <%= end %> + ``` + +* `roles/index.html.erb` + + ```html+erb + <%= render "shared/search_filters", search: @q do |f| %> + <p> + Title contains: <%= f.text_field :title_contains %> + </p> + <%= end %> + ``` + +* `shared/_search_filters.html.erb` + + ```html+erb + <%= form_for(@q) do |f| %> + <h1>Search form:</h1> + <fieldset> + <%= yield f %> + </fieldset> + <p> + <%= f.submit "Search" %> + </p> + <% end %> + ``` + TIP: For content that is shared among all pages in your application, you can use partials directly from layouts. #### Partial Layouts @@ -1119,11 +1199,11 @@ With this change, you can access an instance of the `@products` collection as th You can also pass in arbitrary local variables to any partial you are rendering with the `locals: {}` option: ```erb -<%= render partial: "products", collection: @products, +<%= render partial: "product", collection: @products, as: :item, locals: {title: "Products Page"} %> ``` -Would render a partial `_products.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`. +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. diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index 93729c6f72..50308f505a 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -1,12 +1,33 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Maintenance Policy for Ruby on Rails ==================================== Support of the Rails framework is divided into four groups: New features, bug fixes, security issues, and severe security issues. They are handled as -follows, all versions in x.y.z format +follows, all versions in `X.Y.Z` format. -------------------------------------------------------------------------------- +Rails follows a shifted version of [semver](http://semver.org/): + +**Patch `Z`** + +Only bug fixes, no API changes, no new features. +Except as necessary for security fixes. + +**Minor `Y`** + +New features, may contain API changes (Serve as major versions of Semver). +Breaking changes are paired with deprecation notices in the previous minor +or major release. + +**Major `X`** + +New features, will likely contain API changes. The difference between Rails' +minor and major releases is the magnitude of breaking changes, and usually +reserved for special occasions. + New Features ------------ @@ -20,7 +41,10 @@ Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. -**Currently included series:** 4.0.z +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:** `4.2.Z`, `4.1.Z` (Supported by Rafael França). Security Issues --------------- @@ -35,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:** 4.0.z, 3.2.z +**Currently included series:** `4.2.Z`, `4.1.Z`. Severe Security Issues ---------------------- @@ -44,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:** 4.0.z, 3.2.z +**Currently included series:** `4.2.Z`, `4.1.Z`, `3.2.Z`. Unsupported Release Series -------------------------- diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 855fab18e3..1937369776 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -1,4 +1,6 @@ -Rails nested model forms +**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. @@ -17,9 +19,9 @@ 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 build. +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. +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=`. @@ -54,6 +56,9 @@ class Person < ActiveRecord::Base 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: @@ -220,6 +225,6 @@ As you can see it has generated 2 `project name` inputs, one for each new `proje 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 form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. +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 720ca5d117..10738320ef 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Basics of Creating Rails Plugins ==================================== @@ -45,7 +47,7 @@ $ rails plugin new yaffle See usage and options by asking for help: ```bash -$ rails plugin --help +$ rails plugin new --help ``` Testing Your Newly Generated Plugin @@ -57,7 +59,7 @@ You can navigate to the directory that contains the plugin, run the `bundle inst You should see: ```bash - 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips ``` This will tell you that everything got generated properly and you are ready to start adding functionality. @@ -85,19 +87,19 @@ Run `rake` to run the test. This test should fail because we haven't implemented ```bash 1) Error: - test_to_squawk_prepends_the_word_squawk(CoreExtTest): - NoMethodError: undefined method `to_squawk' for [Hello World](String) - test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' + CoreExtTest#test_to_squawk_prepends_the_word_squawk: + NoMethodError: undefined method `to_squawk' for "Hello World":String + /path/to/yaffle/test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' ``` 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/core_ext' module Yaffle end @@ -118,13 +120,13 @@ end To test that your method does what it says it does, run the unit tests with `rake` from your plugin directory. ```bash - 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips + 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: ```bash -$ rails console +$ bin/rails console >> "Hello World".to_squawk => "squawk! Hello World" ``` @@ -149,7 +151,7 @@ end ```ruby # yaffle/lib/yaffle.rb -require "yaffle/core_ext" +require 'yaffle/core_ext' require 'yaffle/acts_as_yaffle' module Yaffle @@ -196,16 +198,16 @@ When you run `rake`, you should see the following: ``` 1) Error: - test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest): + ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: NameError: uninitialized constant ActsAsYaffleTest::Hickwall - test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' + /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' 2) Error: - test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest): + ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: NameError: uninitialized constant ActsAsYaffleTest::Wickwall - test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' + /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' - 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips + 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips ``` This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test. @@ -214,8 +216,8 @@ test/dummy directory: ```bash $ cd test/dummy -$ rails generate model Hickwall last_squawk:string -$ rails generate model Wickwall last_squawk:string last_tweet:string +$ bin/rails generate model Hickwall last_squawk:string +$ bin/rails generate model Wickwall last_squawk:string last_tweet:string ``` Now you can create the necessary database tables in your testing database by navigating to your dummy app @@ -223,7 +225,7 @@ and migrating the database. First, run: ```bash $ cd test/dummy -$ rake db:migrate +$ bin/rake db:migrate ``` While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act @@ -270,18 +272,18 @@ You can then return to the root directory (`cd ../..`) of your plugin and rerun ``` 1) Error: - test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest): - NoMethodError: undefined method `yaffle_text_field' for #<Class:0x000001016661b8> - /Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing' - test/acts_as_yaffle_test.rb:5:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' + ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: + NoMethodError: undefined method `yaffle_text_field' for #<Class:0x007fd105e3b218> + activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing' + /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' 2) Error: - test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest): - NoMethodError: undefined method `yaffle_text_field' for #<Class:0x00000101653748> - Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing' - test/acts_as_yaffle_test.rb:9:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' + ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: + NoMethodError: undefined method `yaffle_text_field' for #<Class:0x007fd105e409c0> + activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing' + /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' - 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips + 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips ``` @@ -312,7 +314,7 @@ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle When you run `rake`, you should see the tests all pass: ```bash - 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips + 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips ``` ### Add an Instance Method @@ -386,7 +388,7 @@ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle Run `rake` one final time and you should see: ``` - 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips + 6 runs, 6 assertions, 0 failures, 0 errors, 0 skips ``` NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use: @@ -433,12 +435,12 @@ Once your README is solid, go through and add rdoc comments to all of the method Once your comments are good to go, navigate to your plugin directory and run: ```bash -$ rake rdoc +$ bundle exec rake rdoc ``` ### References * [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md) * [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/) -* [Gemspec Reference](http://docs.rubygems.org/read/chapter/20) +* [Gemspec Reference](http://guides.rubygems.org/specification-reference/) * [GemPlugins: A Brief Introduction to the Future of Rails Plugins](http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins) diff --git a/guides/source/profiling.md b/guides/source/profiling.md new file mode 100644 index 0000000000..ce093f78ba --- /dev/null +++ b/guides/source/profiling.md @@ -0,0 +1,16 @@ +*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 e4222e1283..b3e1874048 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Application Templates =========================== @@ -23,8 +25,8 @@ $ rails new blog -m http://example.com/template.rb You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL. ```bash -$ rake rails:template LOCATION=~/template.rb -$ rake rails:template LOCATION=http://example.com/template.rb +$ bin/rake rails:template LOCATION=~/template.rb +$ bin/rake rails:template LOCATION=http://example.com/template.rb ``` Template API @@ -38,9 +40,11 @@ generate(:scaffold, "person name:string") route "root to: 'people#index'" rake("db:migrate") -git :init -git add: "." -git commit: %Q{ -m 'Initial commit' } +after_bundle do + git :init + git add: "." + git commit: %Q{ -m 'Initial commit' } +end ``` The following sections outline the primary methods provided by the API: @@ -228,6 +232,22 @@ git add: "." git commit: "-a -m 'Initial commit'" ``` +### after_bundle(&block) + +Registers a callback to be executed after the gems are bundled and binstubs +are generated. Useful for all generated files to version control: + +```ruby +after_bundle do + git :init + git add: '.' + git commit: "-a -m 'Initial commit'" +end +``` + +The callbacks gets executed even if `--skip-bundle` and/or `--skip-spring` has +been passed. + Advanced Usage -------------- diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 9c92cf3aea..21be74beba 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails on Rack ============= @@ -18,7 +20,7 @@ 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.rubyforge.org/doc/) +* [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. @@ -27,10 +29,9 @@ Rails on Rack ### Rails Application's Rack Object -`ApplicationName::Application` is the primary Rack application object of a Rails +`Rails.application` is the primary Rack application object of a Rails application. Any Rack compliant web server should be using -`ApplicationName::Application` object to serve a Rails -application. `Rails.application` refers to the same application object. +`Rails.application` object to serve a Rails application. ### `rails server` @@ -62,7 +63,6 @@ Here's how it loads the middlewares: ```ruby def middleware middlewares = [] - middlewares << [Rails::Rack::Debugger] if options[:debugger] middlewares << [::Rack::ContentLength] Hash.new(middlewares) end @@ -100,6 +100,10 @@ To find out more about different `rackup` options: $ rackup --help ``` +### Development and auto-reloading + +Middlewares are loaded once and are not monitored for changes. You will have to restart the server for changes to be reflected in the running application. + Action Dispatcher Middleware Stack ---------------------------------- @@ -112,7 +116,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, Rails has a handy rake task for inspecting the middleware stack in use: ```bash -$ rake middleware +$ bin/rake middleware ``` For a freshly generated Rails application, this might produce something like: @@ -141,7 +145,7 @@ use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag -run MyApp::Application.routes +run Rails.application.routes ``` The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below. @@ -195,13 +199,13 @@ And now if you inspect the middleware stack, you'll find that `Rack::Lock` is not a part of it. ```bash -$ rake middleware +$ bin/rake middleware (in /Users/lifo/Rails/blog) use ActionDispatch::Static use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8> use Rack::Runtime ... -run Blog::Application.routes +run Rails.application.routes ``` If you want to remove session related middleware, do the following: @@ -230,7 +234,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::Static`** -* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`. +* Used to serve static files. Disabled if `config.serve_static_files` is `false`. **`Rack::Lock`** @@ -274,7 +278,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::Callbacks`** -* Runs the prepare callbacks before serving the request. +* Provides callbacks to be executed before and after dispatching the request. **`ActiveRecord::Migration::CheckPending`** @@ -304,7 +308,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Parses out parameters from the request into `params`. -**`ActionDispatch::Head`** +**`Rack::Head`** * Converts HEAD requests to `GET` requests and serves them as so. diff --git a/guides/source/routing.md b/guides/source/routing.md index 70d4722068..a689e131ff 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Routing from the Outside In ================================= @@ -183,61 +185,61 @@ You may wish to organize groups of controllers under a namespace. Most commonly, ```ruby namespace :admin do - resources :posts, :comments + resources :articles, :comments end ``` -This will create a number of routes for each of the `posts` and `comments` controller. For `Admin::PostsController`, Rails will create: +This will create a number of routes for each of the `articles` and `comments` controller. For `Admin::ArticlesController`, Rails will create: -| HTTP Verb | Path | Controller#Action | Named Helper | -| --------- | --------------------- | ------------------- | ------------------------- | -| GET | /admin/posts | admin/posts#index | admin_posts_path | -| GET | /admin/posts/new | admin/posts#new | new_admin_post_path | -| POST | /admin/posts | admin/posts#create | admin_posts_path | -| GET | /admin/posts/:id | admin/posts#show | admin_post_path(:id) | -| GET | /admin/posts/:id/edit | admin/posts#edit | edit_admin_post_path(:id) | -| PATCH/PUT | /admin/posts/:id | admin/posts#update | admin_post_path(:id) | -| DELETE | /admin/posts/:id | admin/posts#destroy | admin_post_path(:id) | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | ------------------------ | ---------------------- | ---------------------------- | +| GET | /admin/articles | admin/articles#index | admin_articles_path | +| GET | /admin/articles/new | admin/articles#new | new_admin_article_path | +| POST | /admin/articles | admin/articles#create | admin_articles_path | +| GET | /admin/articles/:id | admin/articles#show | admin_article_path(:id) | +| GET | /admin/articles/:id/edit | admin/articles#edit | edit_admin_article_path(:id) | +| PATCH/PUT | /admin/articles/:id | admin/articles#update | admin_article_path(:id) | +| DELETE | /admin/articles/:id | admin/articles#destroy | admin_article_path(:id) | -If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use: +If you want to route `/articles` (without the prefix `/admin`) to `Admin::ArticlesController`, you could use: ```ruby scope module: 'admin' do - resources :posts, :comments + resources :articles, :comments end ``` or, for a single case: ```ruby -resources :posts, module: 'admin' +resources :articles, module: 'admin' ``` -If you want to route `/admin/posts` to `PostsController` (without the `Admin::` module prefix), you could use: +If you want to route `/admin/articles` to `ArticlesController` (without the `Admin::` module prefix), you could use: ```ruby scope '/admin' do - resources :posts, :comments + resources :articles, :comments end ``` or, for a single case: ```ruby -resources :posts, path: '/admin/posts' +resources :articles, path: '/admin/articles' ``` In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`: -| HTTP Verb | Path | Controller#Action | Named Helper | -| --------- | --------------------- | ----------------- | ------------------- | -| GET | /admin/posts | posts#index | posts_path | -| GET | /admin/posts/new | posts#new | new_post_path | -| POST | /admin/posts | posts#create | posts_path | -| GET | /admin/posts/:id | posts#show | post_path(:id) | -| GET | /admin/posts/:id/edit | posts#edit | edit_post_path(:id) | -| PATCH/PUT | /admin/posts/:id | posts#update | post_path(:id) | -| DELETE | /admin/posts/:id | posts#destroy | post_path(:id) | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | ------------------------ | -------------------- | ---------------------- | +| GET | /admin/articles | articles#index | articles_path | +| GET | /admin/articles/new | articles#new | new_article_path | +| POST | /admin/articles | articles#create | articles_path | +| GET | /admin/articles/:id | articles#show | article_path(:id) | +| GET | /admin/articles/:id/edit | articles#edit | edit_article_path(:id) | +| PATCH/PUT | /admin/articles/:id | articles#update | article_path(:id) | +| DELETE | /admin/articles/:id | articles#destroy | article_path(:id) | TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo' => '/foo#index'`._ @@ -304,7 +306,7 @@ TIP: _Resources should never be nested more than 1 level deep._ One way to avoid deep nesting (as recommended above) is to generate the collection actions scoped under the parent, so as to get a sense of the hierarchy, but to not nest the member actions. In other words, to only build routes with the minimal amount of information to uniquely identify the resource, like this: ```ruby -resources :posts do +resources :articles do resources :comments, only: [:index, :new, :create] end resources :comments, only: [:show, :edit, :update, :destroy] @@ -313,7 +315,7 @@ resources :comments, only: [:show, :edit, :update, :destroy] This idea strikes a balance between descriptive routes and deep nesting. There exists shorthand syntax to achieve just that, via the `:shallow` option: ```ruby -resources :posts do +resources :articles do resources :comments, shallow: true end ``` @@ -321,7 +323,7 @@ end This will generate the exact same routes as the first example. You can also specify the `:shallow` option in the parent resource, in which case all of the nested resources will be shallow: ```ruby -resources :posts, shallow: true do +resources :articles, shallow: true do resources :comments resources :quotes resources :drafts @@ -332,7 +334,7 @@ The `shallow` method of the DSL creates a scope inside of which every nesting is ```ruby shallow do - resources :posts do + resources :articles do resources :comments resources :quotes resources :drafts @@ -344,7 +346,7 @@ There exist two options for `scope` to customize shallow routes. `:shallow_path` ```ruby scope shallow_path: "sekret" do - resources :posts do + resources :articles do resources :comments, shallow: true end end @@ -352,21 +354,21 @@ end The comments resource here will have the following routes generated for it: -| HTTP Verb | Path | Controller#Action | Named Helper | -| --------- | -------------------------------------- | ----------------- | ------------------- | -| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments | -| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments | -| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment | -| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment | -| GET | /sekret/comments/:id(.:format) | comments#show | comment | -| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment | -| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | -------------------------------------------- | ----------------- | ------------------------ | +| GET | /articles/:article_id/comments(.:format) | comments#index | article_comments_path | +| POST | /articles/:article_id/comments(.:format) | comments#create | article_comments_path | +| GET | /articles/:article_id/comments/new(.:format) | comments#new | new_article_comment_path | +| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment_path | +| GET | /sekret/comments/:id(.:format) | comments#show | comment_path | +| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment_path | +| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment_path | The `:shallow_prefix` option adds the specified parameter to the named helpers: ```ruby scope shallow_prefix: "sekret" do - resources :posts do + resources :articles do resources :comments, shallow: true end end @@ -374,15 +376,15 @@ end The comments resource here will have the following routes generated for it: -| HTTP Verb | Path | Controller#Action | Named Helper | -| --------- | -------------------------------------- | ----------------- | ------------------- | -| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments | -| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments | -| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment | -| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment | -| GET | /comments/:id(.:format) | comments#show | sekret_comment | -| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment | -| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | -------------------------------------------- | ----------------- | --------------------------- | +| GET | /articles/:article_id/comments(.:format) | comments#index | article_comments_path | +| POST | /articles/:article_id/comments(.:format) | comments#create | article_comments_path | +| GET | /articles/:article_id/comments/new(.:format) | comments#new | new_article_comment_path | +| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment_path | +| GET | /comments/:id(.:format) | comments#show | sekret_comment_path | +| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment_path | +| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment_path | ### Routing concerns @@ -403,7 +405,7 @@ These concerns can be used in resources to avoid code duplication and share beha ```ruby resources :messages, concerns: :commentable -resources :posts, concerns: [:commentable, :image_attachable] +resources :articles, concerns: [:commentable, :image_attachable] ``` The above is equivalent to: @@ -413,7 +415,7 @@ resources :messages do resources :comments end -resources :posts do +resources :articles do resources :comments resources :images, only: :index end @@ -422,7 +424,7 @@ end Also you can use them in any place that you want inside the routes, for example in a scope or namespace call: ```ruby -namespace :posts do +namespace :articles do concerns :commentable end ``` @@ -631,7 +633,7 @@ This will define a `user_path` method that will be available in controllers, hel ### HTTP Verb Constraints -In general, you should use the `get`, `post`, `put` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once: +In general, you should use the `get`, `post`, `put`, `patch` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once: ```ruby match 'photos', to: 'photos#show', via: [:get, :post] @@ -645,6 +647,8 @@ 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. + ### Segment Constraints You can use the `:constraints` option to enforce a format for a dynamic segment: @@ -662,26 +666,26 @@ get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/ `:constraints` takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work: ```ruby -get '/:id', to: 'posts#show', constraints: {id: /^\d/} +get '/:id', to: 'articles#show', constraints: { id: /^\d/ } ``` However, note that you don't need to use anchors because all routes are anchored at the start. -For example, the following routes would allow for `posts` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace: +For example, the following routes would allow for `articles` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace: ```ruby -get '/:id', to: 'posts#show', constraints: { id: /\d.+/ } +get '/:id', to: 'articles#show', constraints: { id: /\d.+/ } get '/:username', to: 'users#show' ``` ### Request-Based Constraints -You can also constrain a route based on any method on the <a href="action_controller_overview.html#the-request-object">Request</a> object that returns a `String`. +You can also constrain a route based on any method on the [Request object](action_controller_overview.html#the-request-object) that returns a `String`. You specify a request-based constraint the same way that you specify a segment constraint: ```ruby -get 'photos', constraints: {subdomain: 'admin'} +get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' } ``` You can also specify constraints in a block form: @@ -694,6 +698,8 @@ namespace :admin do end ``` +NOTE: Request constraints work by calling a method on the [Request object](action_controller_overview.html#the-request-object) with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String. + ### Advanced Constraints If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do: @@ -709,7 +715,7 @@ class BlacklistConstraint end end -TwitterClone::Application.routes.draw do +Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: BlacklistConstraint.new end @@ -718,7 +724,7 @@ end You can also specify constraints as a lambda: ```ruby -TwitterClone::Application.routes.draw do +Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) } end @@ -752,7 +758,7 @@ get '*a/foo/*b', to: 'test#index' would match `zoo/woo/foo/bar/baz` with `params[:a]` equals `'zoo/woo'`, and `params[:b]` equals `'bar/baz'`. -NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equals to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this: +NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equal to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this: ```ruby get '*pages', to: 'pages#show', format: false @@ -769,20 +775,20 @@ get '*pages', to: 'pages#show', format: true You can redirect any path to another path using the `redirect` helper in your router: ```ruby -get '/stories', to: redirect('/posts') +get '/stories', to: redirect('/articles') ``` You can also reuse dynamic segments from the match in the path to redirect to: ```ruby -get '/stories/:name', to: redirect('/posts/%{name}') +get '/stories/:name', to: redirect('/articles/%{name}') ``` You can also provide a block to redirect, which receives the symbolized path parameters and the request object: ```ruby -get '/stories/:name', to: redirect {|path_params, req| "/posts/#{path_params[:name].pluralize}" } -get '/stories', to: redirect {|path_params, req| "/posts/#{req.subdomain}" } +get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params[:name].pluralize}" } +get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" } ``` Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. @@ -791,7 +797,7 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl ### Routing to Rack Applications -Instead of a String like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher: +Instead of a String like `'articles#index'`, which corresponds to the `index` action in the `ArticlesController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher: ```ruby match '/application.js', to: Sprockets, via: :all @@ -799,7 +805,7 @@ match '/application.js', to: Sprockets, via: :all As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. -NOTE: For the curious, `'posts#index'` actually expands out to `PostsController.action(:index)`, which returns a valid Rack application. +NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application. ### Using `root` @@ -835,7 +841,7 @@ get 'こんにちは', to: 'welcome#index' Customizing Resourceful Routes ------------------------------ -While the default routes and helpers generated by `resources :posts` will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers. +While the default routes and helpers generated by `resources :articles` will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers. ### Specifying a Controller to Use @@ -877,7 +883,7 @@ a warning. You can use the `:constraints` option to specify a required format on the implicit `id`. For example: ```ruby -resources :photos, constraints: {id: /[A-Z][A-Z][0-9]+/} +resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ } ``` This declaration constrains the `:id` parameter to match the supplied regular expression. So, in this case, the router would no longer match `/photos/1` to this route. Instead, `/photos/RR27` would match. @@ -917,7 +923,7 @@ will recognize incoming paths beginning with `/photos` and route the requests to ### Overriding the `new` and `edit` Segments -The `:path_names` option lets you override the automatically-generated "new" and "edit" segments in paths: +The `:path_names` option lets you override the automatically-generated `new` and `edit` segments in paths: ```ruby resources :photos, path_names: { new: 'make', edit: 'change' } @@ -952,7 +958,7 @@ end resources :photos ``` -This will provide route helpers such as `admin_photos_path`, `new_admin_photo_path` etc. +This will provide route helpers such as `admin_photos_path`, `new_admin_photo_path`, etc. To prefix a group of route helpers, use `:as` with `scope`: @@ -972,15 +978,15 @@ You can prefix routes with a named parameter also: ```ruby scope ':username' do - resources :posts + resources :articles end ``` -This will provide you with URLs such as `/bob/posts/1` and will allow you to reference the `username` part of the path as `params[:username]` in controllers, helpers and views. +This will provide you with URLs such as `/bob/articles/1` and will allow you to reference the `username` part of the path as `params[:username]` in controllers, helpers and views. ### Restricting the Routes Created -By default, Rails creates routes for the seven default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the `:only` and `:except` options to fine-tune this behavior. The `:only` option tells Rails to create only the specified routes: +By default, Rails creates routes for the seven default actions (`index`, `show`, `new`, `create`, `edit`, `update`, and `destroy`) for every RESTful route in your application. You can use the `:only` and `:except` options to fine-tune this behavior. The `:only` option tells Rails to create only the specified routes: ```ruby resources :photos, only: [:index, :show] @@ -1042,6 +1048,28 @@ end This will create routing helpers such as `magazine_periodical_ads_url` and `edit_magazine_periodical_ad_path`. +### Overriding Named Route Parameters + +The `:param` option overrides the default resource identifier `:id` (name of +the [dynamic segment](routing.html#dynamic-segments) used to generate the +routes). You can access that segment from your controller using +`params[<:param>]`. + +```ruby +resources :videos, param: :identifier +``` + +``` + videos GET /videos(.:format) videos#index + POST /videos(.:format) videos#create + new_videos GET /videos/new(.:format) videos#new +edit_videos GET /videos/:identifier/edit(.:format) videos#edit +``` + +```ruby +Video.find_by(identifier: params[:identifier]) +``` + Inspecting and Testing Routes ----------------------------- @@ -1070,7 +1098,7 @@ edit_user GET /users/:id/edit(.:format) users#edit You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable: ```bash -$ CONTROLLER=users rake routes +$ CONTROLLER=users bin/rake routes ``` TIP: You'll find that the output from `rake routes` is much more readable if you widen your terminal window until the output lines don't wrap. diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 8faf03e58c..1323742488 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails Guides Guidelines =============================== @@ -13,17 +15,17 @@ After reading this guide, you will know: Markdown ------- -Guides are written in [GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/). There is comprehensive [documentation for Markdown](http://daringfireball.net/projects/markdown/syntax), a [cheatsheet](http://daringfireball.net/projects/markdown/basics), and [additional documentation](http://github.github.com/github-flavored-markdown/) on the differences from traditional Markdown. +Guides are written in [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown). There is comprehensive [documentation for Markdown](http://daringfireball.net/projects/markdown/syntax), as well as a [cheatsheet](http://daringfireball.net/projects/markdown/basics). Prologue -------- -Each guide should start with motivational text at the top (that's the little introduction in the blue area). The prologue should tell the reader what the guide is about, and what they will learn. See for example the [Routing Guide](routing.html). +Each guide should start with motivational text at the top (that's the little introduction in the blue area). The prologue should tell the reader what the guide is about, and what they will learn. As an example, see the [Routing Guide](routing.html). -Titles +Headings ------ -The title of every guide uses `h1`; guide sections use `h2`; subsections `h3`; etc. However, the generated HTML output will have the heading tag starting from `<h2>`. +The title of every guide uses an `h1` heading; guide sections use `h2` headings; subsections use `h3` headings; etc. Note that the generated HTML output will use heading tags starting with `<h2>`. ``` Guide Title @@ -35,14 +37,14 @@ Section ### Sub Section ``` -Capitalize all words except for internal articles, prepositions, conjunctions, and forms of the verb to be: +When writing headings, capitalize all words except for prepositions, conjunctions, internal articles, and forms of the verb "to be": ``` #### Middleware Stack is an Array #### When are Objects Saved? ``` -Use the same typography as in regular text: +Use the same inline formatting as regular text: ``` ##### The `:content_type` Option @@ -51,25 +53,24 @@ Use the same typography as in regular text: API Documentation Guidelines ---------------------------- -The guides and the API should be coherent and consistent where appropriate. Please have a look at these particular sections of the [API Documentation Guidelines](api_documentation_guidelines.html): +The guides and the API should be coherent and consistent where appropriate. In particular, these sections of the [API Documentation Guidelines](api_documentation_guidelines.html) also apply to the guides: * [Wording](api_documentation_guidelines.html#wording) +* [English](api_documentation_guidelines.html#english) * [Example Code](api_documentation_guidelines.html#example-code) -* [Filenames](api_documentation_guidelines.html#filenames) +* [Filenames](api_documentation_guidelines.html#file-names) * [Fonts](api_documentation_guidelines.html#fonts) -Those guidelines apply also to guides. - HTML Guides ----------- Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. -To install the latest version of Bundler, simply run the `gem install bundler` command +To install the latest version of Bundler, run `gem install bundler`. ### Generation -To generate all the guides, just `cd` into the `guides` directory, run `bundle install` and execute: +To generate all the guides, just `cd` into the `guides` directory, run `bundle install`, and execute: ``` bundle exec rake guides:generate diff --git a/guides/source/security.md b/guides/source/security.md index c367604d6f..e486edde31 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails Security Guide ============================ @@ -17,7 +19,7 @@ After reading this guide, you will know: Introduction ------------ -Web application frameworks are made to help developers build web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so that this is hardly a problem. It's nice to see that all of the Rails applications I audited had a good level of security. +Web application frameworks are made to help developers build web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so that this is hardly a problem. In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications). @@ -25,7 +27,7 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. -In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because that's how you find the nasty logical security problems. +In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the [Additional Resources](#additional-resources) chapter). It is done manually because that's how you find the nasty logical security problems. Sessions -------- @@ -60,7 +62,7 @@ Many web applications have an authentication system: a user provides a user name Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: -* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: +* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: ```ruby config.force_ssl = true @@ -68,7 +70,7 @@ Hence, the cookie serves as temporary authentication for the web application. An * Most people don't clear out the cookies after working at a public terminal. So if the last user didn't log out of a web application, you would be able to use it as this user. Provide the user with a _log-out button_ in the web application, and _make it prominent_. -* Many cross-site scripting (XSS) exploits aim at obtaining the user's cookie. You'll read <a href="#cross-site-scripting-xss">more about XSS</a> later. +* Many cross-site scripting (XSS) exploits aim at obtaining the user's cookie. You'll read [more about XSS](#cross-site-scripting-xss) later. * Instead of stealing a cookie unknown to the attacker, they fix a user's session identifier (in the cookie) known to them. Read more about this so-called session fixation later. @@ -81,7 +83,7 @@ Here are some general guidelines on sessions. * _Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below). This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate. -* _Critical data should not be stored in session_. If the user clears his cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data. +* _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 @@ -95,9 +97,16 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves 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_. -`config.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 `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`, e.g.: +`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.: + + development: + secret_key_base: a75d... + + test: + secret_key_base: 492f... - YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...' + production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 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. @@ -111,9 +120,9 @@ It works like this: * A user receives credits, the amount is stored in a session (which is a bad idea anyway, but we'll do this for demonstration purposes). * The user buys something. -* Their new, lower credit will be stored in the session. -* The dark side of the user forces them to take the cookie from the first step (which they copied) and replace the current cookie in the browser. -* The user has their credit back. +* The new adjusted credit value is stored in the session. +* The user takes the cookie from the first step (which they previously copied) and replaces the current cookie in the browser. +* The user has their original credit back. Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database). @@ -128,8 +137,8 @@ NOTE: _Apart from stealing a user's session id, the attacker may fix a session i This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works: * The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image). -* They possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore they access the web application from time to time in order to keep the session alive. -* Now the attacker will force the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on. +* They maintain the session by accessing the web application periodically in order to keep an expiring session alive. +* The attacker forces the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on. * The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id. * As the new trap session is unused, the web application will require the user to authenticate. * From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack. @@ -144,7 +153,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec reset_session ``` -If you use the popular RestfulAuthentication plugin for user management, add reset\_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. +If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. @@ -180,7 +189,7 @@ This attack method works by including malicious code or a link in a page that ac ![](images/csrf.png) -In the <a href="#sessions">session chapter</a> you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is, that it will also send the cookie, if the request comes from a site of a different domain. Let's start with an example: +In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is, that it will also send the cookie, if the request comes from a site of a different domain. Let's start with an example: * Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file. * `<img src="http://www.webapp.com/project/1/destroy">` @@ -191,7 +200,7 @@ In the <a href="#sessions">session chapter</a> you have learned that most Rails It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email. -CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work - _CSRF is an important security issue_. +CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in many security contract works - _CSRF is an important security issue_. ### CSRF Countermeasures @@ -230,28 +239,36 @@ Or the attacker places the code into the onmouseover event handler of an image: <img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." /> ``` -There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request. +There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since `XMLHttpRequest` is subject to the browser Same-Origin policy - meaning only your site can initiate the request. -To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller: +To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications: ```ruby -protect_from_forgery +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, the session will be reset. +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 +`<%= 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: ```ruby -def handle_unverified_request - super - sign_out_user # Example method that will destroy the user cookies. +rescue_from ActionController::InvalidAuthenticityToken do |exception| + sign_out_user # Example method that will destroy the user cookies end ``` -The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present on a non-GET request. +The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present or is incorrect on a non-GET request. -Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later. +Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read [more about XSS](#cross-site-scripting-xss) later. Redirection and Files --------------------- @@ -307,7 +324,7 @@ def sanitize_filename(filename) end ``` -A significant disadvantage of synchronous processing of file uploads (as the attachment\_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server. +A significant disadvantage of synchronous processing of file uploads (as the attachment_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server. The solution to this is best to _process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background. @@ -356,7 +373,7 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend **CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. -A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen. +A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen. Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.
@@ -368,7 +385,7 @@ For _countermeasures against CSRF in administration interfaces and Intranet appl The common admin interface works like this: it's located at www.example.com/admin, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this: -* It is very important to _think about the worst case_: What if someone really got hold of my cookie or user credentials. You could _introduce roles_ for the admin interface to limit the possibilities of the attacker. Or how about _special login credentials_ for the admin interface, other than the ones used for the public part of the application. Or a _special password for very serious actions_? +* It is very important to _think about the worst case_: What if someone really got hold of your cookies or user credentials. You could _introduce roles_ for the admin interface to limit the possibilities of the attacker. Or how about _special login credentials_ for the admin interface, other than the ones used for the public part of the application. Or a _special password for very serious actions_? * Does the admin really have to access the interface from everywhere in the world? Think about _limiting the login to a bunch of source IP addresses_. Examine request.remote_ip to find out about the user's IP address. This is not bullet-proof, but a great barrier. Remember that there might be a proxy in use, though. @@ -400,7 +417,7 @@ If the parameter was nil, the resulting SQL query will be SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1 ``` -And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this. +And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [this blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this. ### Brute-Forcing Accounts @@ -471,7 +488,7 @@ config.filter_parameters << :password INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ -Bruce Schneier, a security technologist, [has analyzed](http://www.schneier.com/blog/archives/2006/12/realworld_passw.html) 34,000 real-world user names and passwords from the MySpace phishing attack mentioned <a href="#examples-from-the-underground">below</a>. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: +Bruce Schneier, a security technologist, [has analyzed](http://www.schneier.com/blog/archives/2006/12/realworld_passw.html) 34,000 real-world user names and passwords from the MySpace phishing attack mentioned [below](#examples-from-the-underground). It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1, and monkey. @@ -549,7 +566,7 @@ Injection is very tricky, because the same code or parameter can be malicious in ### Whitelists versus Blacklists -NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._ +NOTE: _When sanitizing, protecting or verifying something, prefer whitelists over blacklists._ A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_: @@ -624,7 +641,7 @@ Also, the second query renames some columns with the AS statement so that the we #### Countermeasures -Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using `Model.find(id)` or `Model.find_by_some thing(something)` automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (`where("...")`), the `connection.execute()` or `Model.find_by_sql()` methods, it has to be applied manually</em>. +Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. *Using `Model.find(id)` or `Model.find_by_some thing(something)` automatically applies this countermeasure*. But in SQL fragments, especially *in conditions fragments (`where("...")`), the `connection.execute()` or `Model.find_by_sql()` methods, it has to be applied manually*. Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings like this: @@ -726,7 +743,7 @@ Imagine a blacklist deletes "script" from the user input. Now the attacker injec strip_tags("some<<b>script>alert('hello')<</b>/script>") ``` -This returned "some<script>alert('hello')</script>", which makes an attack work. That's why I vote for a whitelist approach, using the updated Rails 2 method sanitize(): +This returned "some<script>alert('hello')</script>", which makes an attack work. That's why a whitelist approach is better, using the updated Rails 2 method sanitize(): ```ruby tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p) @@ -735,7 +752,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 &, ", <, > by their uninterpreted representations in HTML (`&`, `"`, `<`;, and `>`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the [SafeErb](http://safe-erb.rubyforge.org/svn/plugins/safe_erb/) plugin_. SafeErb reminds you to escape strings from external sources. +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 &, ", <, > by their uninterpreted representations in HTML (`&`, `"`, `<`;, and `>`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the SafeErb gem. SafeErb reminds you to escape strings from external sources. ##### Obfuscation and Encoding Injection @@ -806,7 +823,7 @@ The [moz-binding](http://www.securiteam.com/securitynews/5LP051FHPE.html) CSS pr #### Countermeasures -This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, I am not aware of a whitelist CSS filter. _If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application_. Use Rails' `sanitize()` method as a model for a whitelist CSS filter, if you really need one. +This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, it may be hard to find a good whitelist CSS filter. _If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application_. Use Rails' `sanitize()` method as a model for a whitelist CSS filter, if you really need one. ### Textile Injection @@ -841,7 +858,7 @@ It is recommended to _use RedCloth in combination with a whitelist input filter_ NOTE: _The same security precautions have to be taken for Ajax actions as for "normal" ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._ -If you use the [in_place_editor plugin](http://dev.rubyonrails.org/browser/plugins/in_place_editing), or actions that return a string, rather than rendering a view, _you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method. +If you use the [in_place_editor plugin](https://rubygems.org/gems/in_place_editing), or actions that return a string, rather than rendering a view, _you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method. ### Command Line Injection @@ -915,6 +932,49 @@ Content-Type: text/html Under certain circumstances this would present the malicious HTML to the victim. However, this only seems to work with Keep-Alive connections (and many browsers are using one-time connections). But you can't rely on this. _In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._ +Unsafe Query Generation +----------------------- + +Due to the way Active Record interprets parameters in combination with the way +that Rack parses query parameters it was possible to issue unexpected database +queries with `IS NULL` where clauses. As a response to that security issue +([CVE-2012-2660](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/8SA-M3as7A8/Mr9fi9X4kNgJ), +[CVE-2012-2694](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/jILZ34tAHF4/7x0hLH-o0-IJ) +and [CVE-2013-0155](https://groups.google.com/forum/#!searchin/rubyonrails-security/CVE-2012-2660/rubyonrails-security/c7jT-EeN9eI/L0u4e87zYGMJ)) +`deep_munge` method was introduced as a solution to keep Rails secure by default. + +Example of vulnerable code that could be used by attacker, if `deep_munge` +wasn't performed is: + +```ruby +unless params[:token].nil? + user = User.find_by_token(params[:token]) + user.reset_password! +end +``` + +When `params[:token]` is one of: `[nil]`, `[nil, nil, ...]` or +`['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or +`IN ('foo', NULL)` where clauses still will be added to the SQL query. + +To keep rails secure by default, `deep_munge` replaces some of the values with +`nil`. Below table shows what the parameters look like based on `JSON` sent in +request: + +| JSON | Parameters | +|-----------------------------------|--------------------------| +| `{ "person": null }` | `{ :person => nil }` | +| `{ "person": [] }` | `{ :person => [] }` | +| `{ "person": [null] }` | `{ :person => [] }` | +| `{ "person": [null, null, ...] }` | `{ :person => [] }` | +| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` | + +It is possible to return to old behaviour and disable `deep_munge` configuring +your application if you are aware of the risk and know how to handle it: + +```ruby +config.action_dispatch.perform_deep_munge = false +``` Default Headers --------------- @@ -953,7 +1013,7 @@ _'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS at * 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://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html) +[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 @@ -962,14 +1022,13 @@ Used to control which sites are allowed to bypass same origin policies and send 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/initializers/secret_token.rb`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information. +It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, 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. 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: -* The Ruby on Rails security project posts security news regularly: [http://www.rorsecurity.info](http://www.rorsecurity.info) * 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](http://ha.ckers.org/blog/) including the [Cross-Site scripting Cheat Sheet](http://ha.ckers.org/xss.html) diff --git a/guides/source/testing.md b/guides/source/testing.md index 33cd3e868b..8456a8c4a8 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + A Guide to Testing Rails Applications ===================================== @@ -29,11 +31,13 @@ Testing support was woven into the Rails fabric from the beginning. It wasn't an By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`. -A dedicated test database allows you to set up and interact with test data in isolation. Tests can mangle test data with confidence, that won't touch the data in the development or production databases. +A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases. + +Also, each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`. ### Rails Sets up for Testing from the Word Go -Rails creates a `test` folder for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this folder then you shall see: +Rails creates a `test` directory for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this directory then you shall see: ```bash $ ls -F test @@ -41,15 +45,17 @@ controllers/ helpers/ mailers/ test_helper.rb fixtures/ integration/ models/ ``` -The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. +The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers. -Fixtures are a way of organizing test data; they reside in the `fixtures` folder. +Fixtures are a way of organizing test data; they reside in the `fixtures` directory. The `test_helper.rb` file holds the default configuration for your tests. ### The Low-Down on Fixtures -For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures. +For good tests, you'll need to give some thought to setting up test data. +In Rails, you can handle this by defining and customizing fixtures. +You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### What Are Fixtures? @@ -59,7 +65,7 @@ You'll find fixtures under your `test/fixtures` directory. When you run `rails g #### YAML -YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`). +YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`). Here's a sample YAML fixture file: @@ -76,11 +82,11 @@ steve: profession: guy with keyboard ``` -Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them. +Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. 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 define a reference node between two different fixtures. Here's an example with -a belongs_to/has_many association: +a `belongs_to`/`has_many` association: ```yaml # In fixtures/categories.yml @@ -94,6 +100,10 @@ one: category: about ``` +Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`. + +NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). + #### ERB'in It Up ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users: @@ -108,15 +118,17 @@ user_<%= n %>: #### Fixtures in Action -Rails by default automatically loads all fixtures from the `test/fixtures` folder for your models and controllers test. Loading involves three steps: +Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps: * Remove any existing data from the table corresponding to the fixture * Load the fixture data into the table -* Dump the fixture data into a variable in case you want to access it directly +* Dump the fixture data into a method in case you want to access it directly + +TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)) #### Fixtures are Active Record objects -Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example: +Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method whose scope is local of the test case. For example: ```ruby # this will return the User object for the fixture named david @@ -129,32 +141,51 @@ users(:david).id email(david.girlfriend.email, david.location_tonight) ``` +### Rake Tasks for Running your Tests + +Rails comes with a number of built-in rake tasks to help with testing. The +table below lists the commands included in the default Rakefile when a Rails +project is created. + +| Tasks | Description | +| ----------------------- | ----------- | +| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default | +| `rake test:controllers` | Runs all the controller tests from `test/controllers` | +| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` | +| `rake test:helpers` | Runs all the helper tests from `test/helpers` | +| `rake test:integration` | Runs all the integration tests from `test/integration` | +| `rake test:jobs` | Runs all the job tests from `test/jobs` | +| `rake test:mailers` | Runs all the mailer tests from `test/mailers` | +| `rake test:models` | Runs all the model tests from `test/models` | +| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` | +| `rake test:db` | Runs all tests in the `test` directory and resets the db | + +We will cover each of types Rails tests listed above in this guide. + Unit Testing your Models ------------------------ -In Rails, models tests are what you write to test your models. +In Rails, unit tests are what you write to test your models. -For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and will be supplementing it with additional examples where necessary. +For this guide we will be using the application we built in the [Getting Started with Rails](getting_started.html) guide. -NOTE: For more information on Rails <i>scaffolding</i>, refer to [Getting Started with Rails](getting_started.html) - -When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder: +If you remember when you used the `rails generate scaffold` command from earlier. We created our first resource among other things it created a test stub in the `test/models` directory: ```bash -$ rails generate scaffold post title:string body:text +$ bin/rails generate scaffold article title:string body:text ... -create app/models/post.rb -create test/models/post_test.rb -create test/fixtures/posts.yml +create app/models/article.rb +create test/models/article_test.rb +create test/fixtures/articles.yml ... ``` -The default test stub in `test/models/post_test.rb` looks like this: +The default test stub in `test/models/article_test.rb` looks like this: ```ruby require 'test_helper' -class PostTest < ActiveSupport::TestCase +class ArticleTest < ActiveSupport::TestCase # test "the truth" do # assert true # end @@ -167,18 +198,18 @@ A line by line examination of this file will help get you oriented to Rails test require 'test_helper' ``` -As you know by now, `test_helper.rb` specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests. +By requiring this file, `test_helper.rb` the default configuration to run our tests is loaded. We will include this with all the tests we write, so any methods added to this file are available to all your tests. ```ruby -class PostTest < ActiveSupport::TestCase +class ArticleTest < ActiveSupport::TestCase ``` -The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide. +The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. Later in this guide, you'll see some of the methods it gives you. -Any method defined within a class inherited from `MiniTest::Unit::TestCase` -(which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run. +Any method defined within a class inherited from `Minitest::Test` +(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, methods defined as `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run. -Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So, +Rails also adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So you don't have to worry about naming the methods, and you can write something like: ```ruby test "the truth" do @@ -186,7 +217,7 @@ test "the truth" do end ``` -acts as if you had written +Which is approximately the same as writing this: ```ruby def test_the_truth @@ -194,33 +225,44 @@ def test_the_truth end ``` -only the `test` macro allows a more readable test name. You can still use regular method definitions though. +However only the `test` macro allows a more readable test name. You can still use regular method definitions though. + +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. -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. Odd ones need `define_method` and `send` calls, but formally there's no restriction. +Next, let's look at our first assertion: ```ruby assert true ``` -This line of code is called an _assertion_. An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check: +An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check: * does this value = that value? * is this object nil? * does this line of code throw an exception? * is the user's password greater than 5 characters? -Every test contains one or more assertions. Only when all the assertions are successful will the test pass. +Every test must contain at least one assertion, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass. ### Maintaining the test database schema -In order to run your tests, your test database will need to have the current structure. The test helper checks whether your test database has any pending migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` into the test database. If migrations are still pending, an error will be raised. +In order to run your tests, your test database will need to have the current +structure. The test helper checks whether your test database has any pending +migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` +into the test database. If migrations are still pending, an error will be +raised. Usually this indicates that your schema is not fully migrated. Running +the migrations against the development database (`bin/rake db:migrate`) will +bring the schema up to date. + +NOTE: If existing migrations required modifications, the test database needs to +be rebuilt. This can be done by executing `bin/rake db:test:prepare`. ### Running Tests Running a test is as simple as invoking the file containing the test cases through `rake test` command. ```bash -$ rake test test/models/post_test.rb +$ bin/rake test test/models/article_test.rb . Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. @@ -231,7 +273,7 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. You can also run a particular test method from the test case by running the test and providing the `test method name`. ```bash -$ rake test test/models/post_test.rb test_the_truth +$ bin/rake test test/models/article_test.rb test_the_truth . Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. @@ -239,29 +281,31 @@ Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ``` -This will run all test methods from the test case. Note that `test_helper.rb` is in the `test` directory, hence this directory needs to be added to the load path using the `-I` switch. +This will run all test methods from the test case. The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary. -To see how a test failure is reported, you can add a failing test to the `post_test.rb` test case. +#### Your first failing test + +To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case. ```ruby -test "should not save post without title" do - post = Post.new - assert !post.save +test "should not save article without title" do + article = Article.new + assert_not article.save end ``` Let us run this newly added test. ```bash -$ rake test test/models/post_test.rb test_should_not_save_post_without_title +$ bin/rake test test/models/article_test.rb test_should_not_save_article_without_title F Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s. 1) Failure: -test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]: +test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]: Failed assertion, no message given. 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips @@ -270,9 +314,9 @@ Failed assertion, no message given. In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here: ```ruby -test "should not save post without title" do - post = Post.new - assert !post.save, "Saved the post without a title" +test "should not save article without title" do + article = Article.new + assert_not article.save, "Saved the article without a title" end ``` @@ -280,14 +324,14 @@ Running this test shows the friendlier assertion message: ```bash 1) Failure: -test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]: -Saved the post without a title +test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]: +Saved the article without a title ``` Now to get this test to pass we can add a model level validation for the _title_ field. ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base validates :title, presence: true end ``` @@ -295,7 +339,7 @@ end Now the test should pass. Let us verify by running the test again: ```bash -$ rake test test/models/post_test.rb test_should_not_save_post_without_title +$ bin/rake test test/models/article_test.rb test_should_not_save_article_without_title . Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. @@ -305,7 +349,7 @@ Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. Now, if you noticed, we first wrote a test which fails for a desired functionality, then we wrote some code which adds the functionality and finally we ensured that our test passes. This approach to software development is referred to as _Test-Driven Development_ (TDD). -TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an excellent way to build up a test suite that exercises every part of your application. TDD is beyond the scope of this guide, but one place to start is with [15 TDD steps to create a Rails application](http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html). +#### What an error looks like To see how an error gets reported, here's a test containing an error: @@ -320,15 +364,15 @@ end Now you can see even more output in the console from running the tests: ```bash -$ rake test test/models/post_test.rb test_should_report_error +$ bin/rake test test/models/article_test.rb test_should_report_error E Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s. 1) Error: -test_should_report_error(PostTest): -NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0> - test/models/post_test.rb:10:in `block in <class:PostTest>' +test_should_report_error(ArticleTest): +NameError: undefined local variable or method `some_undefined_variable' for #<ArticleTest:0x007fe32e24afe0> + test/models/article_test.rb:10:in `block in <class:ArticleTest>' 1 tests, 0 assertions, 0 failures, 1 errors, 0 skips ``` @@ -339,53 +383,35 @@ NOTE: The execution of each test method stops as soon as any error or an asserti 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 framwork noise and helps to focus on 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 `BACKTRACE` environment variable to enable this behavior: ```bash -$ BACKTRACE=1 rake test test/models/post_test.rb +$ BACKTRACE=1 bin/rake test test/models/article_test.rb ``` -### What to Include in Your Unit Tests +If we want this test to pass we can modify it to use `assert_raises` like so: -Ideally, you would like to include a test for everything which could possibly break. It's a good practice to have at least one test for each of your validations and at least one test for every method in your model. +```ruby +test "should report error" do + # some_undefined_variable is not defined elsewhere in the test case + assert_raises(NameError) do + some_undefined_variable + end +end +``` + +This test should now pass. ### Available Assertions By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned. -There are a bunch of different types of assertions you can use. -Here's an extract of the assertions you can use with `minitest`, the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required. - -| Assertion | Purpose | -| ---------------------------------------------------------------- | ------- | -| `assert( test, [msg] )` | Ensures that `test` is true.| -| `assert_not( test, [msg] )` | Ensures that `test` is false.| -| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.| -| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| -| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.| -| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| -| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.| -| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| -| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.| -| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| -| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.| -| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| -| `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_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.| -| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| -| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| -| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.| -| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| -| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.| -| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| -| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.| -| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| -| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?| -| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.| +There are a bunch of different types of assertions you can use that come with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. + +For a list of all available assertions please check the [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html) Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier. @@ -393,7 +419,7 @@ NOTE: Creating your own assertions is an advanced topic that we won't cover in t ### Rails Specific Assertions -Rails adds some custom assertions of its own to the `test/unit` framework: +Rails adds some custom assertions of its own to the `minitest` framework: | Assertion | Purpose | | --------------------------------------------------------------------------------- | ------- | @@ -401,16 +427,30 @@ Rails adds some custom assertions of its own to the `test/unit` framework: | `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| | `assert_recognizes(expected_options, path, extras={}, message=nil)` | 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)` | 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)` | 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| -| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.| +| `assert_response(type, message = nil)` | 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.| +| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| | `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| You'll see the usage of some of these assertions in the next chapter. +### A Brief Note About Minitest + +All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from: + +* `ActiveSupport::TestCase` +* `ActionController::TestCase` +* `ActionMailer::TestCase` +* `ActionView::TestCase` +* `ActionDispatch::IntegrationTest` + +Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. + +NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html) + Functional Tests for Your Controllers ------------------------------------- -In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view. +In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view. ### What to Include in your Functional Tests @@ -422,26 +462,26 @@ You should test for things such as: * was the correct object stored in the response template? * was the appropriate message displayed to the user in the view? -Now that we have used Rails scaffold generator for our `Post` resource, it has already created the controller code and tests. You can take look at the file `posts_controller_test.rb` in the `test/controllers` directory. +Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory. -Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`. +Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`. ```ruby -class PostsControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionController::TestCase test "should get index" do get :index assert_response :success - assert_not_nil assigns(:posts) + assert_not_nil assigns(:articles) end end ``` -In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `posts` instance variable. +In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `articles` instance variable. The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments: * The action of the controller you are requesting. This can be in the form of a string or a symbol. -* An optional hash of request parameters to pass into the action (eg. query string parameters or post variables). +* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables). * An optional hash of session variables to pass along with the request. * An optional hash of flash values. @@ -457,17 +497,17 @@ Another example: Calling the `:view` action, passing an `id` of 12 as the `param get(:view, {'id' => '12'}, nil, {'message' => 'booya!'}) ``` -NOTE: If you try running `test_should_create_post` test from `posts_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. +NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. -Let us modify `test_should_create_post` test in `posts_controller_test.rb` so that all our test pass: +Let us modify `test_should_create_article` test in `articles_controller_test.rb` so that all our test pass: ```ruby -test "should create post" do - assert_difference('Post.count') do - post :create, post: {title: 'Some title'} +test "should create article" do + assert_difference('Article.count') do + post :create, article: {title: 'Some title'} end - assert_redirected_to post_path(assigns(:post)) + assert_redirected_to article_path(assigns(:article)) end ``` @@ -484,13 +524,13 @@ If you're familiar with the HTTP protocol, you'll know that `get` is a type of r * `head` * `delete` -All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others. +All of request types have equivalent methods that you can use. In a typical C.R.U.D. application you'll be using `get`, `post`, `put` and `delete` more often. -NOTE: Functional tests do not verify whether the specified request type should be accepted by the action. Request types in this context exist to make your tests more descriptive. +NOTE: Functional tests do not verify whether the specified request type is accepted by the action, we're more concerned with the result. Request tests exist for this use case to make your tests more purposeful. ### The Four Hashes of the Apocalypse -After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use: +After a request has been made and processed, you will have 4 Hash objects ready for use: * `assigns` - Any objects that are stored as instance variables in actions for use in views. * `cookies` - Any cookies that are set. @@ -513,13 +553,15 @@ assigns["something"] assigns(:something) You also have access to three instance variables in your functional tests: * `@controller` - The controller processing the request -* `@request` - The request -* `@response` - The response +* `@request` - The request object +* `@response` - The response object ### Setting Headers and CGI variables -Headers and cgi variables can be set directly on the `@request` -instance variable: +[HTTP headers](http://tools.ietf.org/search/rfc2616#section-5.3) +and +[CGI variables](http://tools.ietf.org/search/rfc3875#section-4.1) +can be set directly on the `@request` instance variable: ```ruby # setting a HTTP Header @@ -533,6 +575,10 @@ post :create # simulate the request with custom env variable ### Testing Templates and Layouts +Eventually, you may want to test whether a specific layout is rendered in the view of a response. + +#### Asserting Templates + If you want to make sure that the response rendered the correct template and layout, you can use the `assert_template` method: @@ -541,24 +587,22 @@ test "index should render correct template and layout" do get :index assert_template :index assert_template layout: "layouts/application" + + # You can also pass a regular expression. + assert_template layout: /layouts\/application/ end ``` -Note that you cannot test for template and layout at the same time, with one call to `assert_template` method. -Also, for the `layout` test, you can give a regular expression instead of a string, but using the string, makes -things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout -file in this standard layout directory. Hence, +NOTE: You cannot test for template and layout at the same time, with a single call to `assert_template`. -```ruby -assert_template layout: "application" -``` +WARNING: You must include the "layouts" directory name even if you save your layout file in this standard layout directory. Hence, `assert_template layout: "application"` will not work. -will not work. +#### Asserting Partials -If your view renders any partial, when asserting for the layout, you have to assert for the partial at the same time. +If your view renders any partial, when asserting for the layout, you can to assert for the partial at the same time. Otherwise, assertion will fail. -Hence: +Remember, we added the "_form" partial to our creating Articles view? Let's write an assertion for that in the `:new` action now: ```ruby test "new should render correct layout" do @@ -567,33 +611,207 @@ test "new should render correct layout" do end ``` -is the correct way to assert for the layout when the view renders a partial with name `_form`. Omitting the `:partial` key in your `assert_template` call will complain. +This is the correct way to assert for when the view renders a partial with a given name. As identified by the `:partial` key passed to the `assert_template` call. -### A Fuller Functional Test Example +### Testing `flash` notices -Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`: +If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`. + +We want to add a `flash` message to our blog application whenever someone +successfully creates a new Article. + +Let's start by adding this assertion to our `test_should_create_article` test: ```ruby -test "should create post" do - assert_difference('Post.count') do - post :create, post: {title: 'Hi', body: 'This is my first post.'} +test "should create article" do + assert_difference('Article.count') do + post :create, article: {title: 'Some title'} end - assert_redirected_to post_path(assigns(:post)) - assert_equal 'Post was successfully created.', flash[:notice] + + assert_redirected_to article_path(assigns(:article)) + assert_equal 'Article was successfully created.', flash[:notice] end ``` -### Testing Views +If we run our test now, we should see a failure: + +```bash +$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article +Run options: -n test_should_create_article --seed 32266 + +# Running: -Testing the response to your request by asserting the presence of key HTML elements and their content is a useful way to test the views of your application. The `assert_select` assertion allows you to do this by using a simple yet powerful syntax. +F -NOTE: You may find references to `assert_tag` in other documentation, but this is now deprecated in favor of `assert_select`. +Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s. + + 1) Failure: +ArticlesControllerTest#test_should_create_article [/Users/zzak/code/bench/sharedapp/test/controllers/articles_controller_test.rb:16]: +--- expected ++++ actual +@@ -1 +1 @@ +-"Article was successfully created." ++nil + +1 runs, 4 assertions, 1 failures, 0 errors, 0 skips +``` + +Let's implement the flash message now in our controller. Our `:create` action should now look like this: + +```ruby +def create + @article = Article.new(article_params) + + if @article.save + flash[:notice] = 'Article was successfully created.' + redirect_to @article + else + render 'new' + end +end +``` + +Now if we run our tests, we should see it pass: + +```bash +$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article +Run options: -n test_should_create_article --seed 18981 + +# Running: + +. + +Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s. + +1 runs, 4 assertions, 0 failures, 0 errors, 0 skips +``` + +### Putting it together + +At this point our Articles controller tests the `:index` as well as `:new` and `:create` actions. What about dealing with existing data? + +Let's write a test for the `:show` action: + +```ruby +test "should show article" do + article = articles(:one) + get :show, id: article.id + assert_response :success +end +``` + +Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures. + +How about deleting an existing Article? + +```ruby +test "should destroy article" do + article = articles(:one) + assert_difference('Article.count', -1) do + delete :destroy, id: article.id + end + + assert_redirected_to articles_path +end +``` + +We can also add a test for updating an existing Article. + +```ruby +test "should update article" do + article = articles(:one) + patch :update, id: article.id, article: {title: "updated"} + assert_redirected_to article_path(assigns(:article)) +end +``` + +Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`. + +Our test should now look something like this, disregard the other tests we're leaving them out for brevity. + +```ruby +require 'test_helper' + +class ArticlesControllerTest < ActionController::TestCase + # called before every single test + def setup + @article = articles(:one) + end + + # called after every single test + def teardown + # as we are re-initializing @article before every test + # setting it to nil here is not essential but I hope + # you understand how you can use the teardown method + @article = nil + end + + test "should show article" do + # Reuse the @article instance variable from setup + get :show, id: @article.id + assert_response :success + end + + test "should destroy article" do + assert_difference('Article.count', -1) do + delete :destroy, id: @article.id + end + + assert_redirected_to articles_path + end + + test "should update article" do + patch :update, id: @article.id, article: {title: "updated"} + assert_redirected_to article_path(assigns(:article)) + end +end +``` + +Similar to other callbacks in Rails, the `setup` and `teardown` methods can also be used by passing a block, lambda, or method name as a symbol to call. + +Testing Routes +-------------- + +Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like: + +```ruby +class ArticleRoutesTest < ActionController::TestCase + test "should route to article" do + assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" } + end + + test "should route to create article" do + assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" }) + end +end +``` + +I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see: + +```bash +$ bin/rake test test/controllers/articles_routes_test.rb + +# Running: + +.. + +Finished in 0.069381s, 28.8263 runs/s, 86.4790 assertions/s. + +2 runs, 6 assertions, 0 failures, 0 errors, 0 skips +``` + +For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html). + +Testing Views +------------- + +Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax. There are two forms of `assert_select`: -`assert_select(selector, [equality], [message])` ensures that the equality condition is met on the selected elements through the selector. The selector may be a CSS selector expression (String), an expression with substitution values, or an `HTML::Selector` object. +`assert_select(selector, [equality], [message])` ensures that the equality condition is met on the selected elements through the selector. The selector may be a CSS selector expression (String) or an expression with substitution values. -`assert_select(element, selector, [equality], [message])` ensures that the equality condition is met on all the selected elements through the selector starting from the _element_ (instance of `HTML::Node`) and its descendants. +`assert_select(element, selector, [equality], [message])` ensures that the equality condition is met on all the selected elements through the selector starting from the _element_ (instance of `Nokogiri::XML::Node` or `Nokogiri::XML::NodeSet`) and its descendants. For example, you could verify the contents on the title element in your response with: @@ -601,7 +819,10 @@ For example, you could verify the contents on the title element in your response assert_select 'title', "Welcome to Rails Testing Guide" ``` -You can also use nested `assert_select` blocks. In this case the inner `assert_select` runs the assertion on the complete collection of elements selected by the outer `assert_select` block: +You can also use nested `assert_select` blocks for deeper investigation. + +In the following example, the inner `assert_select` for `li.menu_item` runs +within the collection of elements selected by the outer block: ```ruby assert_select 'ul.navigation' do @@ -609,7 +830,9 @@ assert_select 'ul.navigation' do end ``` -Alternatively the collection of elements selected by the outer `assert_select` may be iterated through so that `assert_select` may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass. +A collection of selected elements may be iterated through so that `assert_select` may be called separately for each element. + +For example if the response contains two ordered lists, each with four nested list elements then the following tests will both pass. ```ruby assert_select "ol" do |elements| @@ -623,7 +846,7 @@ assert_select "ol" do end ``` -The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html). +This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing). #### Additional View-Based Assertions @@ -643,15 +866,48 @@ assert_select_email do end ``` +Testing helpers +--------------- + +In order to test helpers, all you need to do is check that the output of the +helper method matches what you'd expect. Tests related to the helpers are +located under the `test/helpers` directory. + +A helper test looks like so: + +```ruby +require 'test_helper' + +class UserHelperTest < ActionView::TestCase +end +``` + +A helper is just a simple module where you can define methods which are +available into your views. To test the output of the helper's methods, you just +have to use a mixin like this: + +```ruby +class UserHelperTest < ActionView::TestCase + include UserHelper + + test "should return the user name" do + # ... + end +end +``` + +Moreover, since the test class extends from `ActionView::TestCase`, you have +access to Rails' helper methods such as `link_to` or `pluralize`. + Integration Testing ------------------- -Integration tests are used to test the interaction among any number of controllers. They are generally used to test important work flows within your application. +Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application. -Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you. +For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you. ```bash -$ rails generate integration_test user_flows +$ bin/rails generate integration_test user_flows exists test/integration/ create test/integration/user_flows_test.rb ``` @@ -668,237 +924,92 @@ class UserFlowsTest < ActionDispatch::IntegrationTest end ``` -Integration tests inherit from `ActionDispatch::IntegrationTest`. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test. +Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests. ### Helpers Available for Integration Tests -In addition to the standard testing helpers, there are some additional helpers available to integration tests: - -| Helper | Purpose | -| ------------------------------------------------------------------ | ------- | -| `https?` | Returns `true` if the session is mimicking a secure HTTPS request.| -| `https!` | Allows you to mimic a secure HTTPS request.| -| `host!` | Allows you to set the host name to use in the next request.| -| `redirect?` | Returns `true` if the last request was a redirect.| -| `follow_redirect!` | Follows a single redirect response.| -| `request_via_redirect(http_method, path, [parameters], [headers])` | Allows you to make an HTTP request and follow any subsequent redirects.| -| `post_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP POST request and follow any subsequent redirects.| -| `get_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP GET request and follow any subsequent redirects.| -| `patch_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PATCH request and follow any subsequent redirects.| -| `put_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PUT request and follow any subsequent redirects.| -| `delete_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP DELETE request and follow any subsequent redirects.| -| `open_session` | Opens a new session instance.| +In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from. -### Integration Testing Examples +For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html). -A simple integration test that exercises multiple controllers: +When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use. -```ruby -require 'test_helper' +If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help. -class UserFlowsTest < ActionDispatch::IntegrationTest - fixtures :users +### Implementing an integration test - test "login and browse site" do - # login via https - https! - get "/login" - assert_response :success +Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly. - post_via_redirect "/login", username: users(:david).username, password: users(:david).password - assert_equal '/welcome', path - assert_equal 'Welcome david!', flash[:notice] +We'll start by generating our integration test skeleton: - https!(false) - get "/posts/all" - assert_response :success - assert assigns(:products) - end -end +```bash +$ bin/rails generate integration_test blog_flow ``` -As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application. +It should have created a test file placeholder for us, with the output of the previous command you should see: -Here's an example of multiple sessions and custom DSL in an integration test +```bash + invoke test_unit + create test/integration/blog_flow_test.rb +``` + +Now let's open that file and write our first assertion: ```ruby require 'test_helper' -class UserFlowsTest < ActionDispatch::IntegrationTest - fixtures :users - - test "login and browse site" do - - # User david logs in - david = login(:david) - # User guest logs in - guest = login(:guest) - - # Both are now available in different sessions - assert_equal 'Welcome david!', david.flash[:notice] - assert_equal 'Welcome guest!', guest.flash[:notice] - - # User david can browse site - david.browses_site - # User guest can browse site as well - guest.browses_site - - # Continue with other assertions +class BlogFlowTest < ActionDispatch::IntegrationTest + test "can see the welcome page" do + get "/" + assert_select "h1", "Welcome#index" end - - private - - module CustomDsl - def browses_site - get "/products/all" - assert_response :success - assert assigns(:products) - end - end - - def login(user) - open_session do |sess| - sess.extend(CustomDsl) - u = users(user) - sess.https! - sess.post "/login", username: u.username, password: u.password - assert_equal '/welcome', sess.path - sess.https!(false) - end - end end ``` -Rake Tasks for Running your Tests ---------------------------------- +If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request. -You don't need to set up and run your tests by hand on a test-by-test basis. -Rails comes with a number of commands to help in testing. -The table below lists all commands that come along in the default Rakefile -when you initiate a Rails project. +When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass. -| Tasks | Description | -| ----------------------- | ----------- | -| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the tests by default | -| `rake test:controllers` | Runs all the controller tests from `test/controllers` | -| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` | -| `rake test:helpers` | Runs all the helper tests from `test/helpers` | -| `rake test:integration` | Runs all the integration tests from `test/integration` | -| `rake test:mailers` | Runs all the mailer tests from `test/mailers` | -| `rake test:models` | Runs all the model tests from `test/models` | -| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` | -| `rake test:all` | Runs all tests quickly by merging all types and not resetting db | -| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db | +#### Creating articles integration - -Brief Note About `MiniTest` ------------------------------ - -Ruby ships with a boat load of libraries. Ruby 1.8 provides `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing -us to use all of the basic assertions in our tests. - -Ruby 1.9 introduced `MiniTest`, an updated version of `Test::Unit` which provides a backwards compatible API for `Test::Unit`. You could also use `MiniTest` in Ruby 1.8 by installing the `minitest` gem. - -NOTE: For more information on `Test::Unit`, refer to [test/unit Documentation](http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/) -For more information on `MiniTest`, refer to [Minitest](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/minitest/unit/rdoc/) - -Setup and Teardown ------------------- - -If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Posts` controller: +How about testing our ability to create a new article in our blog and see the resulting article. ```ruby -require 'test_helper' - -class PostsControllerTest < ActionController::TestCase - - # called before every single test - def setup - @post = posts(:one) - end - - # called after every single test - def teardown - # as we are re-initializing @post before every test - # setting it to nil here is not essential but I hope - # you understand how you can use the teardown method - @post = nil - end - - test "should show post" do - get :show, id: @post.id - assert_response :success - end - - test "should destroy post" do - assert_difference('Post.count', -1) do - delete :destroy, id: @post.id - end - - assert_redirected_to posts_path - end - +test "can create an article" do + get "/articles/new" + assert_response :success + assert_template "articles/new", partial: "articles/_form" + + post "/articles", article: {title: "can create", body: "article successfully."} + assert_response :redirect + follow_redirect! + assert_response :success + assert_template "articles/show" + assert_select "p", "Title:\n can create" end ``` -Above, the `setup` method is called before each test and so `@post` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using: +Let's break this test down so we can understand it. -* a block -* a method (like in the earlier example) -* a method name as a symbol -* a lambda +We start by calling the `:new` action on our Articles controller. This response should be successful, and we can verify the correct template is rendered including the form partial. -Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol: +After this we make a post request to the `:create` action of our Articles controller: ```ruby -require 'test_helper' - -class PostsControllerTest < ActionController::TestCase - - # called before every single test - setup :initialize_post - - # called after every single test - def teardown - @post = nil - end - - test "should show post" do - get :show, id: @post.id - assert_response :success - end - - test "should update post" do - patch :update, id: @post.id, post: {} - assert_redirected_to post_path(assigns(:post)) - end - - test "should destroy post" do - assert_difference('Post.count', -1) do - delete :destroy, id: @post.id - end - - assert_redirected_to posts_path - end +post "/articles", article: {title: "can create", body: "article successfully."} +assert_response :redirect +follow_redirect! +``` - private +The two lines following the request are to handle the redirect we setup when creating a new article. - def initialize_post - @post = posts(:one) - end -end -``` +NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made. -Testing Routes --------------- +Finally we can assert that our response was successful, template was rendered, and our new article is readable on the page. -Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Posts` controller above should look like: +#### Taking it further -```ruby -test "should route to post" do - assert_routing '/posts/1', {controller: "posts", action: "show", id: "1"} -end -``` +We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications. Testing Your Mailers -------------------- @@ -937,12 +1048,11 @@ Here's a unit test to test a mailer named `UserMailer` whose action `invite` is require 'test_helper' class UserMailerTest < ActionMailer::TestCase - tests UserMailer test "invite" do # Send the email, then test that it got queued email = UserMailer.create_invite('me@example.com', - 'friend@example.com', Time.now).deliver - assert !ActionMailer::Base.deliveries.empty? + 'friend@example.com', Time.now).deliver_now + assert_not ActionMailer::Base.deliveries.empty? # Test the body of the sent email contains what we expect it to assert_equal ['me@example.com'], email.from @@ -996,61 +1106,72 @@ class UserControllerTest < ActionController::TestCase 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) + assert_match(/Hi friend@example.com/, invite_email.body.to_s) end end ``` -Testing helpers ---------------- +Testing Jobs +------------ -In order to test helpers, all you need to do is check that the output of the -helper method matches what you'd expect. Tests related to the helpers are -located under the `test/helpers` directory. Rails provides a generator which -generates both the helper and the test file: +Since your custom jobs can be queued at different levels inside your application, +you'll need to test both jobs themselves (their behavior when they get enqueued) +and that other entities correctly enqueue them. -```bash -$ rails generate helper User - create app/helpers/user_helper.rb - invoke test_unit - create test/helpers/user_helper_test.rb -``` +### A Basic Test Case -The generated test file contains the following code: +By default, when you generate a job, an associated test will be generated as well +under the `test/jobs` directory. Here's an example test with a billing job: ```ruby require 'test_helper' -class UserHelperTest < ActionView::TestCase +class BillingJobTest < ActiveJob::TestCase + test 'that account is charged' do + BillingJob.perform_now(account, product) + assert account.reload.charged_for?(product) + end end ``` -A helper is just a simple module where you can define methods which are -available into your views. To test the output of the helper's methods, you just -have to use a mixin like this: +This test is pretty simple and only asserts that the job get the work done +as expected. + +By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that +your jobs are performed inline. It will also ensure that all previously performed +and enqueued jobs are cleared before any test run so you can safely assume that +no jobs have already been executed in the scope of each test. + +### Custom Assertions And Testing Jobs Inside Other Components + +Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](http://api.rubyonrails.org/classes/ActiveJob/TestHelper.html). + +It's a good practice to ensure that your jobs correctly get enqueued or performed +wherever you invoke them (e.g. inside your controllers). This is precisely where +the custom assertions provided by Active Job are pretty useful. For instance, +within a model: ```ruby -class UserHelperTest < ActionView::TestCase - include UserHelper +require 'test_helper' - test "should return the user name" do - # ... +class ProductTest < ActiveSupport::TestCase + test 'billing job scheduling' do + assert_enqueued_with(job: BillingJob) do + product.charge(account) + end end end ``` -Moreover, since the test class extends from `ActionView::TestCase`, you have -access to Rails' helper methods such as `link_to` or `pluralize`. - Other Testing Approaches ------------------------ -The built-in `test/unit` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including: +The built-in `minitest` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including: * [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use. * [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures. -* [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures. * [Fixture Builder](https://github.com/rdy/fixture_builder), a tool that compiles Ruby factories into fixtures before a test run. * [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests. * [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions. * [RSpec](http://relishapp.com/rspec), a behavior-driven development framework +* [Capybara](http://jnicklas.github.com/capybara/), Acceptance test framework for web applications diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 72a4bb4c2d..d7fbe575c2 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -1,12 +1,16 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + A Guide for 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. +-------------------------------------------------------------------------------- + General Advice -------------- -Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance out several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few. +Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few. ### Test Coverage @@ -16,23 +20,271 @@ The best way to be sure that your application still works after upgrading is to Rails generally stays close to the latest released Ruby version when it's released: -* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible. -* Rails 3.2.x is the last branch to support Ruby 1.8.7. +* Rails 5 requires Ruby 2.2 or newer. * Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer. +* Rails 3.2.x is the last branch to support Ruby 1.8.7. +* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible. TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing. -Upgrading from Rails 4.0 to Rails 4.1 +### The Rake Task + +Rails provides the `rails:update` rake task. After updating the Rails version +in the Gemfile, run this rake task. +This will help you with the creation of new files and changes of old files in an +interactive session. + +```bash +$ rake rails:update + identical config/boot.rb + exist config + conflict config/routes.rb +Overwrite /myapp/config/routes.rb? (enter "h" for help) [Ynaqdh] + force config/routes.rb + conflict config/application.rb +Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh] + force config/application.rb + conflict config/environment.rb +... +``` + +Don't forget to review the difference, to see if there were any unexpected changes. + +Upgrading from Rails 4.2 to Rails 5.0 +------------------------------------- + +### Halting callback chains by returning `false` + +In Rails 4.2, when a 'before' callback returns `false` in ActiveRecord, +ActiveModel and ActiveModel::Validations, then the entire callback chain +is halted. In other words, successive 'before' callbacks are not executed, +and neither is the action wrapped in callbacks. + +In Rails 5.0, returning `false` in a callback will not have this side effect +of halting the callback chain. Instead, callback chains must be explicitly +halted by calling `throw(:abort)`. + +When you upgrade from Rails 4.2 to Rails 5.0, returning `false` in a callback +will still halt the callback chain, but you will receive a deprecation warning +about this upcoming change. + +When you are ready, you can opt into the new behavior and remove the deprecation +warning by adding the following configuration to your `config/application.rb`: + + config.active_support.halt_callback_chains_on_return_false = false + +See [#17227](https://github.com/rails/rails/pull/17227) for more details. + +Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- -NOTE: This section is a work in progress. +### 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. + +### 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: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + respond_to :html, :json + + def show + @user = User.find(params[:id]) + respond_with @user + end +end +``` + +Instance-level `respond_to` is unaffected and does not require the additional gem: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + def show + @user = User.find(params[:id]) + respond_to do |format| + format.html + format.json { render json: @user } + end + end +end +``` + +See [#16526](https://github.com/rails/rails/pull/16526) for more details. + +### Error handling in transaction callbacks + +Currently, Active Record suppresses errors raised +within `after_rollback` or `after_commit` callbacks and only prints them to +the logs. In the next version, these errors will no longer be suppressed. +Instead, the errors will propagate normally just like in other Active +Record callbacks. + +When you define a `after_rollback` or `after_commit` callback, you +will receive a deprecation warning about this upcoming change. When +you are ready, you can opt into the new behavior and remove the +deprecation warning by adding following configuration to your +`config/application.rb`: + + config.active_record.raise_in_transactional_callbacks = true + +See [#14488](https://github.com/rails/rails/pull/14488) and +[#16537](https://github.com/rails/rails/pull/16537) for more details. + +### Ordering of test cases + +In Rails 5.0, test cases will be executed in random order by default. In +anticipation of this change, Rails 4.2 introduced a new configuration option +`active_support.test_order` for explicitly specifying the test ordering. This +allows you to either lock down the current behavior by setting the option to +`:sorted`, or opt into the future behavior by setting the option to `:random`. + +If you do not specify a value for this option, a deprecation warning will be +emitted. To avoid this, add the following line to your test environment: + +```ruby +# config/environments/test.rb +Rails.application.configure do + config.active_support.test_order = :sorted # or `:random` if you prefer +end +``` + +### Serialized attributes + +When using a custom coder (e.g. `serialize :metadata, JSON`), +assigning `nil` to a serialized attribute will save it to the database +as `NULL` instead of passing the `nil` value through the coder (e.g. `"null"` +when using the `JSON` coder). + +### Production log level + +In Rails 5, the default log level for the production environment will be changed +to `:debug` (from `:info`). To preserve the current default, add the following +line to your `production.rb`: + +```ruby +# Set to `:info` to match the current default, or set to `:debug` to opt-into +# the future default. +config.log_level = :info +``` + +### `after_bundle` in Rails templates + +If you have a Rails template that adds all the files in version control, it +fails to add the generated binstubs because it gets executed before Bundler: + +```ruby +# template.rb +generate(:scaffold, "person name:string") +route "root to: 'people#index'" +rake("db:migrate") + +git :init +git add: "." +git commit: %Q{ -m 'Initial commit' } +``` + +You can now wrap the `git` calls in an `after_bundle` block. It will be run +after the binstubs have been generated. + +```ruby +# template.rb +generate(:scaffold, "person name:string") +route "root to: 'people#index'" +rake("db:migrate") + +after_bundle do + git :init + git add: "." + git commit: %Q{ -m 'Initial commit' } +end +``` + +### Rails HTML Sanitizer + +There's a new choice for sanitizing HTML fragments in your applications. The +venerable html-scanner approach is now officially being deprecated in favor of +[`Rails HTML Sanitizer`](https://github.com/rails/rails-html-sanitizer). + +This means the methods `sanitize`, `sanitize_css`, `strip_tags` and +`strip_links` are backed by a new implementation. + +This new sanitizer uses [Loofah](https://github.com/flavorjones/loofah) internally. Loofah in turn uses Nokogiri, which +wraps XML parsers written in both C and Java, so sanitization should be faster +no matter which Ruby version you run. + +The new version updates `sanitize`, so it can take a `Loofah::Scrubber` for +powerful scrubbing. +[See some examples of scrubbers here](https://github.com/flavorjones/loofah#loofahscrubber). + +Two new scrubbers have also been added: `PermitScrubber` and `TargetScrubber`. +Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more information. + +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: + +```ruby +gem 'rails-deprecated_sanitizer' +``` + +### Rails DOM Testing + +The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing). + + +### Masked Authenticity Tokens + +In order to mitigate SSL attacks, `form_authenticity_token` is now masked so that it varies with each request. Thus, tokens are validated by unmasking and then decrypting. As a result, any strategies for verifying requests from non-rails forms that relied on a static session CSRF token have to take this into account. + +### Action Mailer + +Previously, calling a mailer method on a mailer class will result in the +corresponding instance method being executed directly. With the introduction of +Active Job and `#deliver_later`, this is no longer true. In Rails 4.2, the +invocation of the instance methods are deferred until either `deliver_now` or +`deliver_later` is called. For example: + +```ruby +class Notifier < ActionMailer::Base + def notify(user, ...) + puts "Called" + mail(to: user.email, ...) + end +end + +mail = Notifier.notify(user, ...) # Notifier#notify is not yet called at this point +mail = mail.deliver_now # Prints "Called" +``` + +This should not result in any noticeable differences for most applications. +However, if you need some non-mailer methods to be executed synchronously, and +you were previously relying on the synchronous proxying behavior, you should +define them as class methods on the mailer class directly: + +```ruby +class Notifier < ActionMailer::Base + def self.broadcast_notifications(users, ...) + users.each { |user| Notifier.notify(user, ...) } + end +end +``` + +Upgrading from Rails 4.0 to Rails 4.1 +------------------------------------- ### CSRF protection from remote `<script>` tags Or, "whaaat my tests are failing!!!?" Cross-site request forgery (CSRF) protection now covers GET requests with -JavaScript responses, too. That prevents a third-party site from referencing +JavaScript responses, too. This prevents a third-party site from referencing your JavaScript URL and attempting to run it to extract sensitive data. This means that your functional and integration tests that use @@ -47,7 +299,7 @@ will now trigger CSRF protection. Switch to xhr :get, :index, format: :js ``` -to explicitly test an XmlHttpRequest. +to explicitly test an `XmlHttpRequest`. If you really mean to load JavaScript from remote `<script>` tags, skip CSRF protection on that action. @@ -62,7 +314,7 @@ If you want to use Spring as your application preloader you need to: NOTE: User defined rake tasks will run in the `development` environment by default. If you want them to run in other environments consult the -[Spring README](https://github.com/jonleighton/spring#rake). +[Spring README](https://github.com/rails/spring#rake). ### `config/secrets.yml` @@ -79,11 +331,14 @@ secrets, you need to: secret_key_base: production: - secret_key_base: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> ``` -2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to - `secrets.yml` under the `production` section. +2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to + set the SECRET_KEY_BASE environment variable for whichever users running the + Rails application in production mode. Alternatively, you can simply copy the existing + `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml` + under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'. 3. Remove the `secret_token.rb` initializer. @@ -95,9 +350,66 @@ secrets, you need to: If your test helper contains a call to `ActiveRecord::Migration.check_pending!` this can be removed. The check -is now done automatically when you `require 'test_help'`, although +is now done automatically when you `require 'rails/test_help'`, although leaving this line in your helper is not harmful in any way. +### Cookies serializer + +Applications created before Rails 4.1 uses `Marshal` to serialize cookie values into +the signed and encrypted cookie jars. If you want to use the new `JSON`-based format +in your application, you can add an initializer file with the following content: + +```ruby +Rails.application.config.action_dispatch.cookies_serializer = :hybrid +``` + +This would transparently migrate your existing `Marshal`-serialized cookies into the +new `JSON`-based format. + +When using the `:json` or `:hybrid` serializer, you should beware that not all +Ruby objects can be serialized as JSON. For example, `Date` and `Time` objects +will be serialized as strings, and `Hash`es will have their keys stringified. + +```ruby +class CookiesController < ApplicationController + def set_cookie + cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014 + redirect_to action: 'read_cookie' + end + + def read_cookie + cookies.encrypted[:expiration_date] # => "2014-03-20" + end +end +``` + +It's advisable that you only store simple data (strings and numbers) in cookies. +If you have to store complex objects, you would need to handle the conversion +manually when reading the values on subsequent requests. + +If you use the cookie session store, this would apply to the `session` and +`flash` hash as well. + +### Flash structure changes + +Flash message keys are +[normalized to strings](https://github.com/rails/rails/commit/a668beffd64106a1e1fedb71cc25eaaa11baf0c1). They +can still be accessed using either symbols or strings. Looping through the flash +will always yield string keys: + +```ruby +flash["string"] = "a string" +flash[:symbol] = "a symbol" + +# Rails < 4.1 +flash.keys # => ["string", :symbol] + +# Rails >= 4.1 +flash.keys # => ["string", "symbol"] +``` + +Make sure you are comparing Flash message keys against strings. + ### Changes in JSON handling There are a few major changes related to JSON handling in Rails 4.1. @@ -130,7 +442,7 @@ Rails-specific features. For example: ```ruby class FooBar def as_json(options = nil) - { foo: "bar" } + { foo: 'bar' } end end @@ -148,10 +460,20 @@ part of the rewrite, the following features have been removed from the encoder: 2. Support for the `encode_json` hook 3. Option to encode `BigDecimal` objects as numbers instead of strings -If you application depends on one of these features, you can get them back by +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. +#### JSON representation of Time objects + +`#as_json` for objects with time component (`Time`, `DateTime`, `ActiveSupport::TimeWithZone`) +now returns millisecond precision by default. If you need to keep old behavior with no millisecond +precision, set the following in an initializer: + +``` +ActiveSupport::JSON::Encoding.time_precision = 0 +``` + ### Usage of `return` within inline callback blocks Previously, Rails allowed inline callback blocks to use `return` this way: @@ -162,7 +484,7 @@ class ReadOnlyModel < ActiveRecord::Base end ``` -This behaviour was never intentionally supported. Due to a change in the internals +This behavior was never intentionally supported. Due to a change in the internals of `ActiveSupport::Callbacks`, this is no longer allowed in Rails 4.1. Using a `return` statement in an inline callback block causes a `LocalJumpError` to be raised when the callback is executed. @@ -207,7 +529,7 @@ included in the newly introduced `ActiveRecord::FixtureSet.context_class`, in `test_helper.rb`. ```ruby -class FixtureFileHelpers +module FixtureFileHelpers def file_sha(path) Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path))) end @@ -217,8 +539,8 @@ ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers ### I18n enforcing available locales -Rails 4.1 now defaults the I18n option `enforce_available_locales` to `true`, -meaning that it will make sure that all locales passed to it must be declared in +Rails 4.1 now defaults the I18n option `enforce_available_locales` to `true`. This +means that it will make sure that all locales passed to it must be declared in the `available_locales` list. To disable it (and allow I18n to accept *any* locale option) add the following @@ -228,9 +550,10 @@ configuration to your application: config.i18n.enforce_available_locales = false ``` -Note that this option was added as a security measure, to ensure user input could -not be used as locale information unless previously known, so it's recommended not -to disable this option unless you have a strong reason for doing so. +Note that this option was added as a security measure, to ensure user input +cannot be used as locale information unless it is previously known. Therefore, +it's recommended not to disable this option unless you have a strong reason for +doing so. ### Mutator methods called on Relation @@ -249,6 +572,114 @@ authors = Author.where(name: 'Hank Moody').to_a authors.compact! ``` +### Changes on Default Scopes + +Default scopes are no longer overridden by chained conditions. + +In previous versions when you defined a `default_scope` in a model +it was overridden by chained conditions in the same field. Now it +is merged like any other scope. + +Before: + +```ruby +class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } +end + +User.all +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + +User.active +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' + +User.where(state: 'inactive') +# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +``` + +After: + +```ruby +class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } +end + +User.all +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + +User.active +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' + +User.where(state: 'inactive') +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' +``` + +To get the previous behavior it is needed to explicitly remove the +`default_scope` condition using `unscoped`, `unscope`, `rewhere` or +`except`. + +```ruby +class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { unscope(where: :state).where(state: 'active') } + scope :inactive, -> { rewhere state: 'inactive' } +end + +User.all +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + +User.active +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' + +User.inactive +# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +``` + +### Rendering content from string + +Rails 4.1 introduces `:plain`, `:html`, and `:body` options to `render`. Those +options are now the preferred way to render string-based content, as it allows +you to specify which content type you want the response sent as. + +* `render :plain` will set the content type to `text/plain` +* `render :html` will set the content type to `text/html` +* `render :body` will *not* set the content type header. + +From the security standpoint, if you don't expect to have any markup in your +response body, you should be using `render :plain` as most browsers will escape +unsafe content in the response for you. + +We will be deprecating the use of `render :text` in a future version. So please +start using the more precise `:plain`, `:html`, and `:body` options instead. +Using `render :text` may pose a security risk, as the content is sent as +`text/html`. + +### PostgreSQL json and hstore datatypes + +Rails 4.1 will map `json` and `hstore` columns to a string-keyed Ruby `Hash`. +In earlier versions, a `HashWithIndifferentAccess` was used. This means that +symbol access is no longer supported. This is also the case for +`store_accessors` based on top of `json` or `hstore` columns. Make sure to use +string keys consistently. + +### Explicit block use for `ActiveSupport::Callbacks` + +Rails 4.1 now expects an explicit block to be passed when calling +`ActiveSupport::Callbacks.set_callback`. This change stems from +`ActiveSupport::Callbacks` being largely rewritten for the 4.1 release. + +```ruby +# Previously in Rails 4.0 +set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff } + +# Now in Rails 4.1 +set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } +``` + Upgrading from Rails 3.2 to Rails 4.0 ------------------------------------- @@ -320,7 +751,7 @@ being used, you can update your form to use the `PUT` method instead: <%= form_for [ :update_name, @user ], method: :put do |f| %> ``` -For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/) +For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/) on the Rails blog. #### A note about media types @@ -336,7 +767,7 @@ def update respond_to do |format| format.json do # perform a partial update - @post.update params[:post] + @article.update params[:article] end format.json_patch do @@ -363,7 +794,7 @@ file (in `config/application.rb`): ```ruby # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) +Bundler.require(*Rails.groups) ``` ### vendor/plugins @@ -380,6 +811,9 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. You shouldn't use instance methods since it's now deprecated. You should change them to use class methods, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`. +* When using the default coder, assigning `nil` to a serialized attribute will save it +to the database as `NULL` instead of passing the `nil` value through YAML (`"--- \n...\n"`). + * Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) for a smooth upgrade path. * If you are not using Protected Attributes, you can remove any options related to @@ -399,7 +833,7 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options. * Rails 4.0 has deprecated `ActiveRecord::TestCase` in favor of `ActiveSupport::TestCase`. * Rails 4.0 has deprecated the old-style hash based finder API. This means that - methods which previously accepted "finder options" no longer do. + methods which previously accepted "finder options" no longer do. For example, `Book.find(:all, conditions: { name: '1984' })` has been deprecated in favor of `Book.where(name: '1984')` * All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's how you can handle the changes: @@ -424,7 +858,7 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur * Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail, the error will be attached to `:#{attribute}_confirmation` instead of `attribute`. -* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behaviour. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file: +* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behavior. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file: ```ruby # Disable root element in JSON by default. @@ -503,13 +937,13 @@ get 'こんにちは', controller: 'welcome', action: 'index' ```ruby # Rails 3.x - match "/" => "root#index" + match '/' => 'root#index' # becomes - match "/" => "root#index", via: :get + match '/' => 'root#index', via: :get # or - get "/" => "root#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`. @@ -550,13 +984,12 @@ The order in which helpers from more than one directory are loaded has changed i ### Active Record Observer and Action Controller Sweeper -Active Record Observer and Action Controller Sweeper have been extracted to the `rails-observers` gem. You will need to add the `rails-observers` gem if you require these features. +`ActiveRecord::Observer` and `ActionController::Caching::Sweeper` have been extracted to the `rails-observers` gem. You will need to add the `rails-observers` gem if you require these features. ### sprockets-rails -* `assets:precompile:primary` has been removed. Use `assets:precompile` instead. -* The `config.assets.compress` option should be changed to -`config.assets.js_compressor` like so for instance: +* `assets:precompile:primary` and `assets:precompile:all` have been removed. Use `assets:precompile` instead. +* The `config.assets.compress` option should be changed to `config.assets.js_compressor` like so for instance: ```ruby config.assets.js_compressor = :uglifier @@ -564,22 +997,23 @@ config.assets.js_compressor = :uglifier ### sass-rails -* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` +* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`. Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- -If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2. +If your application is currently on any version of Rails older than 3.1.x, you +should upgrade to Rails 3.1 before attempting an update to Rails 3.2. -The following changes are meant for upgrading your application to Rails 3.2.16, -the last 3.2.x version of Rails. +The following changes are meant for upgrading your application to the latest +3.2.x version of Rails. ### Gemfile Make the following changes to your `Gemfile`. ```ruby -gem 'rails', '3.2.16' +gem 'rails', '3.2.21' group :assets do gem 'sass-rails', '~> 3.2.6' @@ -704,8 +1138,8 @@ You can help test performance with these additions to your test environment: ```ruby # Configure static asset server for tests with Cache-Control for performance -config.serve_static_assets = true -config.static_cache_control = "public, max-age=3600" +config.serve_static_files = true +config.static_cache_control = 'public, max-age=3600' ``` ### config/initializers/wrap_parameters.rb @@ -740,7 +1174,7 @@ AppName::Application.config.session_store :cookie_store, key: 'SOMETHINGNEW' or ```bash -$ rake db:sessions:clear +$ bin/rake db:sessions:clear ``` ### Remove :cache and :concat options in asset helpers references in views diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 3c04204c29..367abddcfa 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Working with JavaScript in Rails ================================ @@ -111,7 +113,9 @@ paintIt = (element, backgroundColor, textColor) -> element.style.color = textColor $ -> - $("a[data-background-color]").click -> + $("a[data-background-color]").click (e) -> + e.preventDefault() + backgroundColor = $(this).data("background-color") textColor = $(this).data("text-color") paintIt(this, backgroundColor, textColor) @@ -156,7 +160,7 @@ is a helper that assists with writing forms. `form_for` takes a `:remote` option. It works like this: ```erb -<%= form_for(@post, remote: true) do |f| %> +<%= form_for(@article, remote: true) do |f| %> ... <% end %> ``` @@ -164,7 +168,7 @@ option. It works like this: This will generate the following HTML: ```html -<form accept-charset="UTF-8" action="/posts" class="new_post" data-remote="true" id="new_post" method="post"> +<form accept-charset="UTF-8" action="/articles" class="new_article" data-remote="true" id="new_article" method="post"> ... </form> ``` @@ -178,10 +182,10 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out: ```coffeescript $(document).ready -> - $("#new_post").on("ajax:success", (e, data, status, xhr) -> - $("#new_post").append xhr.responseText - ).bind "ajax:error", (e, xhr, status, error) -> - $("#new_post").append "<p>ERROR</p>" + $("#new_article").on("ajax:success", (e, data, status, xhr) -> + $("#new_article").append xhr.responseText + ).on "ajax:error", (e, xhr, status, error) -> + $("#new_article").append "<p>ERROR</p>" ``` Obviously, you'll want to be a bit more sophisticated than that, but it's a @@ -194,7 +198,7 @@ is very similar to `form_for`. It has a `:remote` option that you can use like this: ```erb -<%= form_tag('/posts', remote: true) do %> +<%= form_tag('/articles', remote: true) do %> ... <% end %> ``` @@ -202,7 +206,7 @@ this: This will generate the following HTML: ```html -<form accept-charset="UTF-8" action="/posts" data-remote="true" method="post"> +<form accept-charset="UTF-8" action="/articles" data-remote="true" method="post"> ... </form> ``` @@ -217,21 +221,21 @@ is a helper that assists with generating links. It has a `:remote` option you can use like this: ```erb -<%= link_to "a post", @post, remote: true %> +<%= link_to "an article", @article, remote: true %> ``` which generates ```html -<a href="/posts/1" data-remote="true">a post</a> +<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 -assume that we have a list of posts that can be deleted with just one +assume that we have a list of articles that can be deleted with just one click. We would generate some HTML like this: ```erb -<%= link_to "Delete post", @post, remote: true, method: :delete %> +<%= link_to "Delete article", @article, remote: true, method: :delete %> ``` and write some CoffeeScript like this: @@ -239,7 +243,7 @@ and write some CoffeeScript like this: ```coffeescript $ -> $("a[data-remote]").on "ajax:success", (e, data, status, xhr) -> - alert "The post was deleted." + alert "The article was deleted." ``` ### button_to @@ -247,14 +251,14 @@ $ -> [`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: ```erb -<%= button_to "A post", @post, remote: true %> +<%= button_to "An article", @article, remote: true %> ``` this generates ```html -<form action="/posts/1" class="button_to" data-remote="true" method="post"> - <div><input type="submit" value="A post"></div> +<form action="/articles/1" class="button_to" data-remote="true" method="post"> + <div><input type="submit" value="An article"></div> </form> ``` diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb index 6ef3df45a9..71f044b9c4 100644 --- a/guides/w3c_validator.rb +++ b/guides/w3c_validator.rb @@ -60,6 +60,8 @@ module RailsGuides def guides_to_validate guides = Dir["./output/*.html"] guides.delete("./output/layout.html") + guides.delete("./output/_license.html") + guides.delete("./output/_welcome.html") ENV.key?('ONLY') ? select_only(guides) : guides end |