diff options
250 files changed, 3812 insertions, 1752 deletions
diff --git a/.travis.yml b/.travis.yml index 9e7a449010..16086e2663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +services: memcache script: 'ci/travis.rb' before_install: - travis_retry gem install bundler @@ -11,6 +11,7 @@ gem 'rack-cache', '~> 1.2' gem 'jquery-rails', '~> 3.1.0' gem 'turbolinks' gem 'coffee-rails', '~> 4.0.0' +gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: '2-1-stable' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) @@ -76,7 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the ## Code Status -* [![Build Status](https://travis-ci.org/rails/rails.png?branch=master)](https://travis-ci.org/rails/rails) +* [![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails) ## License diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index 9b06d0d162..c6c1c12e87 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -158,7 +158,7 @@ commits should be added to the release branch besides regression fixing commits. == Day of release Many of these steps are the same as for the release candidate, so if you need -more explanation on a particular step, so the RC steps. +more explanation on a particular step, see the RC steps. Today, do this stuff in this order: diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index fe5b38d29f..7e3a426eb2 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,31 @@ +* Add alias `ActionDispatch::Http::UploadedFile#to_io` to + `ActionDispatch::Http::UploadedFile#tempfile`. + + *Tim Linquist* + +* Returns null type format when format is not know and controller is using `any` + format block. + + Fixes #14462. + + *Rafael Mendonça França* + +* Improve routing error page with fuzzy matching search. + + *Winston* + +* Only make deeply nested routes shallow when parent is shallow. + + Fixes #14684. + + *Andrew White*, *James Coglan* + +* Append link to bad code to backtrace when exception is SyntaxError. + + *Boris Kuznetsov* + * Swapped the parameters of assert_equal in `assert_select` so that the - proper values were printed correctly + proper values were printed correctly Fixes #14422. diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index b84c9e78c3..0f4cc7a8f5 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -70,7 +70,8 @@ module ActionController # can do the following: # # class HelloController < ActionController::Metal - # include ActionController::Rendering + # include AbstractController::Rendering + # include ActionView::Layouts # append_view_path "#{Rails.root}/app/views" # # def index diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index aff083b502..d86d49c9dc 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -502,7 +502,7 @@ module ActionController # end # end # - # In order to use <tt>accepts_nested_attribute_for</tt> with Strong \Parameters, you + # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you # will need to specify which nested attributes should be whitelisted. # # class Person diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index df57efaa97..caaebc537a 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -17,8 +17,9 @@ module ActionController @_templates = Hash.new(0) @_layouts = Hash.new(0) @_files = Hash.new(0) + @_subscribers = [] - ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload| + @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:layout] if path @_layouts[path] += 1 @@ -28,7 +29,7 @@ module ActionController end end - ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| + @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:virtual_path] next unless path partial = path =~ /^.*\/_[^\/]*$/ @@ -41,7 +42,7 @@ module ActionController @_templates[path] += 1 end - ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| + @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| next if payload[:virtual_path] # files don't have virtual path path = payload[:identifier] @@ -53,8 +54,9 @@ module ActionController end def teardown_subscriptions - ActiveSupport::Notifications.unsubscribe("render_template.action_view") - ActiveSupport::Notifications.unsubscribe("!render_template.action_view") + @_subscribers.each do |subscriber| + ActiveSupport::Notifications.unsubscribe(subscriber) + end end def process(*args) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index b803ce8b6f..0b2b60d2e4 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -129,7 +129,7 @@ module ActionDispatch end end - order.include?(Mime::ALL) ? formats.first : nil + order.include?(Mime::ALL) ? format : nil end protected diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index a8d2dc3950..45bf751d09 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -18,6 +18,7 @@ module ActionDispatch # A +Tempfile+ object with the actual uploaded file. Note that some of # its interface is available directly. attr_accessor :tempfile + alias :to_io :tempfile # A string with the headers of the multipart request. attr_accessor :headers diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index 254c2befc4..94b0a24344 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -19,6 +19,14 @@ module ActionDispatch end def simulate(string) + ms = memos(string) { return } + MatchData.new(ms) + end + + alias :=~ :simulate + alias :match :simulate + + def memos(string) input = StringScanner.new(string) state = [0] while sym = input.scan(%r([/.?]|[^/.?]+)) @@ -29,15 +37,10 @@ module ActionDispatch tt.accepting? s } - return if acceptance_states.empty? + return yield if acceptance_states.empty? - memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact - - MatchData.new(memos) + acceptance_states.flat_map { |x| tt.memo(x) }.compact end - - alias :=~ :simulate - alias :match :simulate end end end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index e6212b1ee2..990d2127ee 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -40,7 +40,19 @@ module ActionDispatch end def move(t, a) - move_string(t, a).concat(move_regexp(t, a)) + return [] if t.empty? + + regexps = [] + + t.map { |s| + if states = @regexp_states[s] + regexps.concat states.map { |re, v| re === a ? v : nil } + end + + if states = @string_states[s] + states[a] + end + }.compact.concat regexps end def as_json(options = nil) @@ -139,26 +151,6 @@ module ActionDispatch raise ArgumentError, 'unknown symbol: %s' % sym.class end end - - def move_regexp(t, a) - return [] if t.empty? - - t.flat_map { |s| - if states = @regexp_states[s] - states.map { |re, v| re === a ? v : nil } - end - }.compact.uniq - end - - def move_string(t, a) - return [] if t.empty? - - t.map do |s| - if states = @string_states[s] - states[a] - end - end.compact - end end end end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 419e665d12..36561c71a1 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -121,8 +121,7 @@ module ActionDispatch def filter_routes(path) return [] unless ast - data = simulator.match(path) - data ? data.memos : [] + simulator.memos(path) { [] } end def find_routes env diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 377f05c982..2326bb043a 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -32,6 +32,8 @@ module ActionDispatch def initialize(env, exception) @env = env @exception = original_exception(exception) + + expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError) end def rescue_template @@ -104,5 +106,11 @@ module ActionDispatch end end end + + def expand_backtrace + @exception.backtrace.unshift( + @exception.to_s.split("\n") + ).flatten! + end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 3be0ce1860..0864e7ef2a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -29,7 +29,7 @@ module ActionDispatch # # Configure your session store in config/initializers/session_store.rb: # - # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session' + # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # # Configure your secret key in config/secrets.yml: # diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 999c022535..0c7caef25d 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -32,11 +32,14 @@ module ActionDispatch private def redirect_to_https(request) - url = URI(request.url) - url.scheme = "https" - url.host = @host if @host - url.port = @port if @port - headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s } + host = @host || request.host + port = @port || request.port + + location = "https://#{host}" + location << ":#{port}" if port != 80 + location << request.fullpath + + headers = { 'Content-Type' => 'text/html', 'Location' => location } [301, headers, []] end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb index f154021ae6..f154021ae6 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb new file mode 100644 index 0000000000..603de54b8b --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb @@ -0,0 +1,9 @@ +<%= @exception.class.to_s %><% + if @request.parameters['controller'] +%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> +<% end %> + +<%= @exception.message %> +<%= render template: "rescues/_source" %> +<%= render template: "rescues/_trace" %> +<%= render template: "rescues/_request_and_response" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 323873ba4b..cce0d75af4 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -4,21 +4,41 @@ border-collapse: collapse; } - #route_table td { - padding: 0 30px; + #route_table thead tr { + border-bottom: 2px solid #ddd; + } + + #route_table thead tr.bottom { + border-bottom: none; } - #route_table tr.bottom th { - padding-bottom: 10px; + #route_table thead tr.bottom th { + padding: 10px 0; line-height: 15px; } - #route_table .matched_paths { + #route_table tbody tr { + border-bottom: 1px solid #ddd; + } + + #route_table tbody tr:nth-child(odd) { + background: #f2f2f2; + } + + #route_table tbody.exact_matches, + #route_table tbody.fuzzy_matches { background-color: LightGoldenRodYellow; + border-bottom: solid 2px SlateGrey; } - #route_table .matched_paths { - border-bottom: solid 3px SlateGrey; + #route_table tbody.exact_matches tr, + #route_table tbody.fuzzy_matches tr { + background: none; + border-bottom: none; + } + + #route_table td { + padding: 4px 30px; } #path_search { @@ -45,13 +65,15 @@ <th><%# HTTP Verb %> </th> <th><%# Path %> - <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %> + <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %> </th> <th><%# Controller#action %> </th> </tr> </thead> - <tbody class='matched_paths' id='matched_paths'> + <tbody class='exact_matches' id='exact_matches'> + </tbody> + <tbody class='fuzzy_matches' id='fuzzy_matches'> </tbody> <tbody> <%= yield %> @@ -59,6 +81,7 @@ </table> <script type='text/javascript'> + // Iterates each element through a function function each(elems, func) { if (!elems instanceof Array) { elems = [elems]; } for (var i = 0, len = elems.length; i < len; i++) { @@ -66,77 +89,110 @@ } } - function setValOn(elems, val) { - each(elems, function(elem) { - elem.innerHTML = val; - }); + // Sets innerHTML for an element + function setContent(elem, text) { + elem.innerHTML = text; } - function onClick(elems, func) { - each(elems, function(elem) { - elem.onclick = func; - }); - } + // Enables path search functionality + function setupMatchPaths() { + // Check if the user input (sanitized as a path) matches the regexp data attribute + function checkExactMatch(section, elem, value) { + var string = sanitizePath(value), + regexp = elem.getAttribute("data-regexp"); - // Enables functionality to toggle between `_path` and `_url` helper suffixes - function setupRouteToggleHelperLinks() { - var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); - onClick(toggleLinks, function(){ - var helperTxt = this.getAttribute("data-route-helper"), - helperElems = document.querySelectorAll('[data-route-name] span.helper'); - setValOn(helperElems, helperTxt); - }); - } + showMatch(string, regexp, section, elem); + } - // takes an array of elements with a data-regexp attribute and - // passes their parent <tr> into the callback function - // if the regexp matches a given path - function eachElemsForPath(elems, path, func) { - each(elems, function(e){ - var reg = e.getAttribute("data-regexp"); - if (path.match(RegExp(reg))) { - func(e.parentNode.cloneNode(true)); - } - }) - } + // Check if the route path data attribute contains the user input + function checkFuzzyMatch(section, elem, value) { + var string = elem.getAttribute("data-route-path"), + regexp = value; - // Ensure path always starts with a slash "/" and remove params or fragments - function sanitizePath(path) { - var path = path.charAt(0) == '/' ? path : "/" + path; - return path.replace(/\#.*|\?.*/, ''); - } + showMatch(string, regexp, section, elem); + } - // Enables path search functionality - function setupMatchPaths() { - var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), - pathElem = document.querySelector('#path_search'), - selectedSection = document.querySelector('#matched_paths'), - noMatchText = '<tr><th colspan="4">None</th></tr>'; + // Display the parent <tr> element in the appropriate section when there's a match + function showMatch(string, regexp, section, elem) { + if(string.match(RegExp(regexp))) { + section.appendChild(elem.parentNode.cloneNode(true)); + } + } + + // Check if there are any matched results in a section + function checkNoMatch(section, defaultText, noMatchText) { + if (section.innerHTML === defaultText) { + setContent(section, defaultText + noMatchText); + } + } + // Ensure path always starts with a slash "/" and remove params or fragments + function sanitizePath(path) { + var path = path.charAt(0) == '/' ? path : "/" + path; + return path.replace(/\#.*|\?.*/, ''); + } - // Remove matches if no path is present - pathElem.onblur = function(e) { - if (pathElem.value === "") selectedSection.innerHTML = ""; + var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), + searchElem = document.querySelector('#search'), + exactMatches = document.querySelector('#exact_matches'), + fuzzyMatches = document.querySelector('#fuzzy_matches'); + + // Remove matches when no search value is present + searchElem.onblur = function(e) { + if (searchElem.value === "") { + setContent(exactMatches, ""); + setContent(fuzzyMatches, ""); + } } // On key press perform a search for matching paths - pathElem.onkeyup = function(e){ - var path = sanitizePath(pathElem.value), - defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>'; + searchElem.onkeyup = function(e){ + var userInput = searchElem.value, + defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>', + defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</th></tr>', + noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>', + noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>'; // Clear out results section - selectedSection.innerHTML= defaultText; + setContent(exactMatches, defaultExactMatch); + setContent(fuzzyMatches, defaultFuzzyMatch); + + // Display exact matches and fuzzy matches + each(regexpElems, function(elem) { + checkExactMatch(exactMatches, elem, userInput); + checkFuzzyMatch(fuzzyMatches, elem, userInput); + }) + + // Display 'No Matches' message when no matches are found + checkNoMatch(exactMatches, defaultExactMatch, noExactMatch); + checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch); + } + } - // Display matches if they exist - eachElemsForPath(regexpElems, path, function(e){ - selectedSection.appendChild(e); + // Enables functionality to toggle between `_path` and `_url` helper suffixes + function setupRouteToggleHelperLinks() { + + // Sets content for each element + function setValOn(elems, val) { + each(elems, function(elem) { + setContent(elem, val); }); + } - // If no match present, tell the user - if (selectedSection.innerHTML === defaultText) { - selectedSection.innerHTML = selectedSection.innerHTML + noMatchText; - } + // Sets onClick event for each element + function onClick(elems, func) { + each(elems, function(elem) { + elem.onclick = func; + }); } + + var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); + onClick(toggleLinks, function(){ + var helperTxt = this.getAttribute("data-route-helper"), + helperElems = document.querySelectorAll('[data-route-name] span.helper'); + + setValOn(helperElems, helperTxt); + }); } setupMatchPaths(); diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index a9ac2bce1d..9cd884daa3 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -11,7 +11,7 @@ module ActionDispatch # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # AppName::Application.routes.draw do + # Rails.application.routes.draw do # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6f0b49cf28..77718a14c1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -995,6 +995,7 @@ module ActionDispatch @as = options[:as] @param = (options[:param] || :id).to_sym @options = options + @shallow = false end def default_actions @@ -1055,6 +1056,13 @@ module ActionDispatch "#{path}/:#{nested_param}" end + def shallow=(value) + @shallow = value + end + + def shallow? + @shallow + end end class SingletonResource < Resource #:nodoc: @@ -1361,7 +1369,7 @@ module ActionDispatch end with_scope_level(:nested) do - if shallow? && nesting_depth > 1 + if shallow? && shallow_nesting_depth > 1 shallow_scope(parent_resource.nested_scope, nested_options) { yield } else scope(parent_resource.nested_scope, nested_options) { yield } @@ -1576,6 +1584,7 @@ module ActionDispatch end def resource_scope(kind, resource) #:nodoc: + resource.shallow = @scope[:shallow] old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource @nesting.push(resource) @@ -1600,6 +1609,10 @@ module ActionDispatch @nesting.size end + def shallow_nesting_depth #:nodoc: + @nesting.select(&:shallow?).size + end + def param_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 8a128427bf..12023e6f77 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -267,7 +267,7 @@ module ActionDispatch text.strip! unless NO_STRIP.include?(match.name) text.sub!(/\A\n/, '') if match.name == "textarea" unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text) + content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text) true end end @@ -276,7 +276,7 @@ module ActionDispatch html = match.children.map(&:to_s).join html.strip! unless NO_STRIP.include?(match.name) unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, html) + content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html) true end end @@ -289,7 +289,7 @@ module ActionDispatch # FIXME: minitest provides messaging when we use assert_operator, # so is this custom message really needed? - message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.) + message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}) if count assert_equal count, matches.size, message else diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 114bbf3c22..580604df3a 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -79,13 +79,13 @@ class AssertSelectTest < ActionController::TestCase def test_assert_select render_html %Q{<div id="1"></div><div id="2"></div>} assert_select "div", 2 - assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" } + assert_failure(/\AExpected at least 1 element matching \"p\", found 0\.$/) { assert_select "p" } end def test_equality_integer render_html %Q{<div id="1"></div><div id="2"></div>} - assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 } - assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 } + assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) { assert_select "div", 3 } + assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", 0 } end def test_equality_true_false @@ -100,13 +100,14 @@ class AssertSelectTest < ActionController::TestCase def test_equality_false_message render_html %Q{<div id="1"></div><div id="2"></div>} - assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false } + assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", false } end def test_equality_string_and_regexp render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", "foo" } assert_raise(Assertion) { assert_select "div", "bar" } + assert_failure(/\A<bar> expected but was\n<foo>\.$/) { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", :text=>"foo" } assert_raise(Assertion) { assert_select "div", :text=>"bar" } assert_nothing_raised { assert_select "div", /(foo|bar)/ } @@ -124,6 +125,7 @@ class AssertSelectTest < ActionController::TestCase assert_raise(Assertion) { assert_select "p", html } assert_nothing_raised { assert_select "p", :html=>html } assert_raise(Assertion) { assert_select "p", :html=>text } + assert_failure(/\A<#{text}> expected but was\n<#{html}>\.$/) { assert_select "p", :html=>text } # No stripping for pre. render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>} text = "\n\"This is not a big problem,\" he said.\n" @@ -144,29 +146,29 @@ class AssertSelectTest < ActionController::TestCase def test_counts render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", 2 } - assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do + assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do assert_select "div", 3 end assert_nothing_raised { assert_select "div", 1..2 } - assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do + assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do assert_select "div", 3..4 end assert_nothing_raised { assert_select "div", :count=>2 } - assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do + assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do assert_select "div", :count=>3 end assert_nothing_raised { assert_select "div", :minimum=>1 } assert_nothing_raised { assert_select "div", :minimum=>2 } - assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do + assert_failure(/\AExpected at least 3 elements matching \"div\", found 2\.$/) do assert_select "div", :minimum=>3 end assert_nothing_raised { assert_select "div", :maximum=>2 } assert_nothing_raised { assert_select "div", :maximum=>3 } - assert_failure(/Expected at most 1 element matching \"div\", found 2/) do + assert_failure(/\AExpected at most 1 element matching \"div\", found 2\.$/) do assert_select "div", :maximum=>1 end assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 } - assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do + assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do assert_select "div", :minimum=>3, :maximum=>4 end end @@ -204,7 +206,7 @@ class AssertSelectTest < ActionController::TestCase end end - assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do + assert_failure(/\AExpected at least 1 element matching \"#4\", found 0\.$/) do assert_select "div" do assert_select "#4" end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 25a4857eba..3720a920d0 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -210,20 +210,29 @@ class FlashTest < ActionController::TestCase end def test_redirect_to_with_adding_flash_types - @controller.class.add_flash_types :foo + original_controller = @controller + test_controller_with_flash_type_foo = Class.new(TestController) do + add_flash_types :foo + end + @controller = test_controller_with_flash_type_foo.new get :redirect_with_foo_flash assert_equal "for great justice", @controller.send(:flash)[:foo] + ensure + @controller = original_controller end - class SubclassesTestController < TestController; end - def test_add_flash_type_to_subclasses - TestController.add_flash_types :foo - assert SubclassesTestController._flash_types.include?(:foo) + test_controller_with_flash_type_foo = Class.new(TestController) do + add_flash_types :foo + end + subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo) + assert subclass_controller_with_no_flash_type._flash_types.include?(:foo) end - def test_do_not_add_flash_type_to_parent_class - SubclassesTestController.add_flash_types :bar + def test_does_not_add_flash_type_to_parent_class + Class.new(TestController) do + add_flash_types :bar + end assert_not TestController._flash_types.include?(:bar) end end diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 499c62cc35..ce6d135d92 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -492,6 +492,11 @@ class RespondToControllerTest < ActionController::TestCase assert_equal 'Whatever you ask for, I got it', @response.body end + def test_handle_any_any_unkown_format + get :handle_any_any, { format: 'php' } + assert_equal 'Whatever you ask for, I got it', @response.body + end + def test_browser_check_with_any_any @request.accept = "application/json, application/xml" get :json_xml_or_html diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 99229b3baf..5ab5141966 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -138,14 +138,14 @@ module RequestForgeryProtectionTests assert_not_blocked do get :index end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_button_to_with_token_tag assert_not_blocked do get :show_button end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_without_token_tag_if_remote @@ -175,7 +175,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' ensure ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original end @@ -185,21 +185,21 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested assert_not_blocked do get :form_for_remote_with_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_with_token_tag_with_authenticity_token_requested assert_not_blocked do get :form_for_with_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_allow_get diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 3045a07ad6..8660deb634 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -43,6 +43,19 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest raise ActionController::UrlGenerationError, "No route matches" when "/parameter_missing" raise ActionController::ParameterMissing, :missing_param_key + when "/original_syntax_error" + eval 'broke_syntax =' # `eval` need for raise native SyntaxError at runtime + when "/syntax_error_into_view" + begin + eval 'broke_syntax =' + rescue Exception => e + template = ActionView::Template.new(File.read(__FILE__), + __FILE__, + ActionView::Template::Handlers::Raw.new, + {}) + raise ActionView::Template::Error.new(template, e) + end + else raise "puke!" end @@ -134,9 +147,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/", {}, xhr_request_env assert_response 500 + assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) assert_equal response.content_type, "text/plain" - assert_match(/puke/, body) + assert_match(/RuntimeError\npuke/, body) get "/not_found", {}, xhr_request_env assert_response 404 @@ -242,4 +256,26 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/", {}, env assert_operator((output.rewind && output.read).lines.count, :>, 10) end + + test 'display backtrace when error type is SyntaxError' do + @app = DevelopmentApp + + get '/original_syntax_error', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new} + + assert_response 500 + assert_select '#Application-Trace' do + assert_select 'pre code', /\(eval\):1: syntax error, unexpected/ + end + end + + test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do + @app = DevelopmentApp + + get '/syntax_error_into_view', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new} + + assert_response 500 + assert_select '#Application-Trace' do + assert_select 'pre code', /\(eval\):1: syntax error, unexpected/ + end + end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index ab2f0ec8de..f74a0ef945 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1994,6 +1994,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'cards#destroy', @response.body end + def test_shallow_deeply_nested_resources + draw do + resources :blogs do + resources :posts do + resources :comments, shallow: true + end + end + end + + get '/comments/1' + assert_equal 'comments#show', @response.body + + assert_equal '/comments/1', comment_path('1') + assert_equal '/blogs/new', new_blog_path + assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1) + assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2) + end + def test_shallow_nested_resources_within_scope draw do scope '/hello' do diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 94969f795a..c3598c5e8e 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -196,6 +196,13 @@ class SSLTest < ActionDispatch::IntegrationTest response.headers['Location'] end + def test_redirect_to_host_with_port + self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org:443") + get "http://example.org/path?key=value" + assert_equal "https://ssl.example.org:443/path?key=value", + response.headers['Location'] + end + def test_redirect_to_secure_host_when_on_subdomain self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org") get "http://ssl.example.org/path?key=value" diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 72f3d1db0d..9f6381f118 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -33,6 +33,12 @@ module ActionDispatch assert_equal 'foo', uf.tempfile end + def test_to_io_returns_the_tempfile + tf = Object.new + uf = Http::UploadedFile.new(:tempfile => tf) + assert_equal tf, uf.to_io + end + def test_delegates_path_to_tempfile tf = Class.new { def path; 'thunderhorse' end } uf = Http::UploadedFile.new(:tempfile => tf.new) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 8c6db33be7..8578b43d78 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,17 @@ +* Remove wrapping div with inline styles for hidden form fields. + + We are dropping HTML 4.01 and XHTML strict compliance since input tags directly + inside a form are valid HTML5, and the absense of inline styles help in validating + for Content Security Policy. + + *Joost Baaij* + +* `collection_check_boxes` respects `:index` option for the hidden filed name. + + Fixes #14147. + + *Vasiliy Ermolovich* + * `date_select` helper with option `with_css_classes: true` does not overwrite other classes. *Izumi Wong-Horiuchi* diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index af70a4242a..227ad4cdfa 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -10,7 +10,7 @@ module ActionView # Full usage example: # # config/routes.rb: - # Basecamp::Application.routes.draw do + # Rails.application.routes.draw do # resources :posts # root to: "posts#index" # end diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index d1c268ec40..4db8930a26 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -11,7 +11,7 @@ module ActionView # The best way to use this is by doing key-based cache expiration # on top of a cache store like Memcached that'll automatically # kick out old entries. For more on key-based expiration, see: - # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works + # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works # # When using this method, you list the cache dependency as the name of the cache, like so: # diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index f625a9ff49..48f42947db 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -360,8 +360,8 @@ module ActionView html_attributes = option_html_attributes(element) text, value = option_text_and_value(element).map { |item| item.to_s } - html_attributes[:selected] = option_value_selected?(value, selected) - html_attributes[:disabled] = disabled && option_value_selected?(value, disabled) + html_attributes[:selected] ||= option_value_selected?(value, selected) + html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled) html_attributes[:value] = value content_tag_string(:option, text, html_attributes) diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 0bbe08166b..66c9e20682 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -550,6 +550,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # color_field_tag 'name' + # # => <input id="name" name="name" type="color" /> + # + # color_field_tag 'color', '#DEF726' + # # => <input id="color" name="color" type="color" value="#DEF726" /> + # + # color_field_tag 'color', nil, class: 'special_input' + # # => <input class="special_input" id="color" name="color" type="color" /> + # + # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" /> def color_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "color")) end @@ -558,6 +571,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # search_field_tag 'name' + # # => <input id="name" name="name" type="search" /> + # + # search_field_tag 'search', 'Enter your search query here' + # # => <input id="search" name="search" type="search" value="Enter your search query here" /> + # + # search_field_tag 'search', nil, class: 'special_input' + # # => <input class="special_input" id="search" name="search" type="search" /> + # + # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" /> def search_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "search")) end @@ -566,6 +592,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # telephone_field_tag 'name' + # # => <input id="name" name="name" type="tel" /> + # + # telephone_field_tag 'tel', '0123456789' + # # => <input id="tel" name="tel" type="tel" value="0123456789" /> + # + # telephone_field_tag 'tel', nil, class: 'special_input' + # # => <input class="special_input" id="tel" name="tel" type="tel" /> + # + # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" /> def telephone_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "tel")) end @@ -638,6 +677,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # url_field_tag 'name' + # # => <input id="name" name="name" type="url" /> + # + # url_field_tag 'url', 'http://rubyonrails.org' + # # => <input id="url" name="url" type="url" value="http://rubyonrails.org" /> + # + # url_field_tag 'url', nil, class: 'special_input' + # # => <input class="special_input" id="url" name="url" type="url" /> + # + # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" /> def url_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "url")) end @@ -726,9 +778,11 @@ module ActionView method_tag(method) + token_tag(authenticity_token) end - enforce_utf8 = html_options.delete("enforce_utf8") { true } - tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag - content_tag(:div, tags, :style => 'display:none') + if html_options.delete("enforce_utf8") { true } + utf8_enforcer_tag + method_tag + else + method_tag + end end def form_tag_html(html_options) diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 8b28e4fc33..6242a2a085 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -28,10 +28,7 @@ module ActionView # Append a hidden field to make sure something will be sent back to the # server if all check boxes are unchecked. if @options.fetch(:include_hidden, true) - hidden_name = @html_options[:name] || "#{tag_name}[]" - hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil) - - rendered_collection + hidden + rendered_collection + hidden_field else rendered_collection end @@ -42,6 +39,18 @@ module ActionView def render_component(builder) builder.check_box + builder.label end + + def hidden_field + hidden_name = @html_options[:name] + + hidden_name ||= if @options.has_key?(:index) + "#{tag_name_with_index(@options[:index])}[]" + else + "#{tag_name}[]" + end + + @template_object.hidden_field_tag(hidden_name, "", id: nil) + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 991f32cea2..8050638363 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -44,7 +44,7 @@ module ActionView def default_html_options_for_collection(item, value) #:nodoc: html_options = @html_options.dup - [:checked, :selected, :disabled].each do |option| + [:checked, :selected, :disabled, :readonly].each do |option| current_value = @options[option] next if current_value.nil? diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 89c196e578..894616a449 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -323,7 +323,7 @@ module ActionView inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param) end end - content_tag('form', content_tag('div', inner_tags), form_options) + content_tag('form', inner_tags, form_options) end # Creates a link tag of the given +name+ using a URL created by the set of diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 3145446114..9e8e6f43d5 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -235,7 +235,8 @@ module ActionView :@options, :@test_passed, :@view, - :@view_context_class + :@view_context_class, + :@_subscribers ] def _user_defined_ivars diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb index 0a9628da8d..0a62f49f35 100644 --- a/actionview/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb @@ -59,12 +59,13 @@ class FormHelperActiveRecordTest < ActionView::TestCase protected def hidden_fields(method = nil) - txt = %{<div style="display:none">} - txt << %{<input name="utf8" type="hidden" value="✓" />} + txt = %{<input name="utf8" type="hidden" value="✓" />} + if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} end - txt << %{</div>} + + txt end def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) @@ -88,4 +89,4 @@ class FormHelperActiveRecordTest < ActionView::TestCase form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" end -end
\ No newline at end of file +end diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 73fa3b6b4e..5e991d87ad 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -68,6 +68,23 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=radio][value=false][disabled=disabled]' end + test 'collection radio accepts multiple readonly items' do + collection = [[1, true], [0, false], [2, 'other']] + with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => [true, false] + + assert_select 'input[type=radio][value=true][readonly=readonly]' + assert_select 'input[type=radio][value=false][readonly=readonly]' + assert_no_select 'input[type=radio][value=other][readonly=readonly]' + end + + test 'collection radio accepts single readonly item' do + collection = [[1, true], [0, false]] + with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => true + + assert_select 'input[type=radio][value=true][readonly=readonly]' + assert_no_select 'input[type=radio][value=false][readonly=readonly]' + end + test 'collection radio accepts html options as input' do collection = [[1, true], [0, false]] with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio' @@ -204,6 +221,13 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1 end + test 'collection check boxes generates a hidden field with index if it was provided' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, { index: 322 } + + assert_select "input[type=hidden][name='user[322][category_ids][]'][value=]", count: 1 + end + test 'collection check boxes does not generate a hidden field if include_hidden option is false' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false @@ -325,6 +349,33 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=checkbox][value=2][disabled=disabled]' end + test 'collection check boxes accepts multiple readonly items' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => [1, 3] + + assert_select 'input[type=checkbox][value=1][readonly=readonly]' + assert_select 'input[type=checkbox][value=3][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=2][readonly=readonly]' + end + + test 'collection check boxes accepts single readonly item' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => 1 + + assert_select 'input[type=checkbox][value=1][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=3][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=2][readonly=readonly]' + end + + test 'collection check boxes accepts a proc to readonly items' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => proc { |i| i.first == 1 } + + assert_select 'input[type=checkbox][value=1][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=3][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=2][readonly=readonly]' + end + test 'collection check boxes accepts html options' do collection = [[1, 'Category 1'], [2, 'Category 2']] with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check' diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index b5e9801776..90fe9fdc6a 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1420,7 +1420,7 @@ class FormHelperTest < ActionView::TestCase expected = whole_form("/posts", "new_post", "new_post") do "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" + "<label for='post_1_tag_ids_1'>Tag 1</label>" + - "<input name='post[tag_ids][]' type='hidden' value='' />" + "<input name='post[1][tag_ids][]' type='hidden' value='' />" end assert_dom_equal expected, output_buffer @@ -3020,12 +3020,13 @@ class FormHelperTest < ActionView::TestCase protected def hidden_fields(method = nil) - txt = %{<div style="display:none">} - txt << %{<input name="utf8" type="hidden" value="✓" />} + txt = %{<input name="utf8" type="hidden" value="✓" />} + if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} end - txt << %{</div>} + + txt end def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index 50e9d132a7..fbafb7aa08 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -119,6 +119,26 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_array_options_for_select_with_custom_defined_selected + assert_dom_equal( + "<option selected=\"selected\" type=\"Coach\" value=\"1\">Richard Bandler</option>\n<option type=\"Coachee\" value=\"1\">Richard Bandler</option>", + options_for_select([ + ['Richard Bandler', 1, { type: 'Coach', selected: 'selected' }], + ['Richard Bandler', 1, { type: 'Coachee' }] + ]) + ) + end + + def test_array_options_for_select_with_custom_defined_disabled + assert_dom_equal( + "<option disabled=\"disabled\" type=\"Coach\" value=\"1\">Richard Bandler</option>\n<option type=\"Coachee\" value=\"1\">Richard Bandler</option>", + options_for_select([ + ['Richard Bandler', 1, { type: 'Coach', disabled: 'disabled' }], + ['Richard Bandler', 1, { type: 'Coachee' }] + ]) + ) + end + def test_array_options_for_select_with_selection assert_dom_equal( "<option value=\"Denmark\">Denmark</option>\n<option value=\"<USA>\" selected=\"selected\"><USA></option>\n<option value=\"Sweden\">Sweden</option>", @@ -813,7 +833,7 @@ class FormOptionsHelperTest < ActionView::TestCase select("post", "category", %w( one two ), :selected => 'two', :prompt => true) ) end - + def test_select_with_disabled_array @post = Post.new @post.category = "<mus>" diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index cf824e2733..18c739674a 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -14,12 +14,15 @@ class FormTagHelperTest < ActionView::TestCase method = options[:method] enforce_utf8 = options.fetch(:enforce_utf8, true) - txt = %{<div style="display:none">} - txt << %{<input name="utf8" type="hidden" value="✓" />} if enforce_utf8 - if method && !%w(get post).include?(method.to_s) - txt << %{<input name="_method" type="hidden" value="#{method}" />} + ''.tap do |txt| + if enforce_utf8 + txt << %{<input name="utf8" type="hidden" value="✓" />} + end + + if method && !%w(get post).include?(method.to_s) + txt << %{<input name="_method" type="hidden" value="#{method}" />} + end end - txt << %{</div>} end def form_text(action = "http://www.example.com", options = {}) diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 7e978e15d2..35279a4558 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -53,12 +53,12 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_straight_url - assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") + assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com") end def test_button_to_with_path assert_dom_equal( - %{<form method="post" action="/article/Hello" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="/article/Hello" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", article_path("Hello".html_safe)) ) end @@ -67,7 +67,7 @@ class UrlHelperTest < ActiveSupport::TestCase self.request_forgery = true assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></form>}, button_to("Hello", "http://www.example.com") ) ensure @@ -75,102 +75,102 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_form_class - assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') end def test_button_to_with_form_class_escapes - assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') end def test_button_to_with_query - assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2") end def test_button_to_with_html_safe_URL - assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) end def test_button_to_with_query_and_no_name - assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&q2=v2" /></div></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&q2=v2" class="button_to"><input type="submit" value="http://www.example.com?q1=v1&q2=v2" /></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2") end def test_button_to_with_javascript_confirm assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" }) ) end def test_button_to_with_javascript_disable_with assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", data: { disable_with: "Greeting..." }) ) end def test_button_to_with_remote_and_form_options assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: true, form: { class: "custom-class", "data-type" => "json" }) ) end def test_button_to_with_remote_and_javascript_confirm assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: true, data: { confirm: "Are you sure?" }) ) end def test_button_to_with_remote_and_javascript_disable_with assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: true, data: { disable_with: "Greeting..." }) ) end def test_button_to_with_remote_false assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: false) ) end def test_button_to_enabled_disabled assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", disabled: false) ) assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input disabled="disabled" type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input disabled="disabled" type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", disabled: true) ) end def test_button_to_with_method_delete assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", method: :delete) ) end def test_button_to_with_method_get assert_dom_equal( - %{<form method="get" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="get" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", method: :get) ) end def test_button_to_with_block assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><button type="submit"><span>Hello</span></button></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><button type="submit"><span>Hello</span></button></form>}, button_to("http://www.example.com") { content_tag(:span, 'Hello') } ) end def test_button_to_with_params assert_dom_equal( - %{<form action="http://www.example.com" class="button_to" method="post"><div><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></div></form>}, + %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>}, button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"}) ) end diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 3e49c34182..feb3d9371d 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -48,6 +48,7 @@ module ActiveModel eager_autoload do autoload :Errors + autoload :StrictValidationFailed, 'active_model/errors' end module Serializers diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 7022f9bee5..ff41572105 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -53,7 +53,7 @@ module ActiveModel # # Configuration options: # * <tt>:on</tt> - Specifies when this validation is active - # (<tt>:create</tt> or <tt>:update</tt>. + # (<tt>:create</tt> or <tt>:update</tt>). # * <tt>:if</tt> - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index fe18ae995c..0cba1009b6 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,176 @@ +* Fixed has_many association to make it support irregular inflections. + + Fixes #8928. + + *arthurnn*, *Javier Goizueta* + +* Fixed a problem where count used with a grouping was not returning a Hash. + + Fixes #14721. + + *Eric Chahin* + +* `sanitize_sql_like` helper method to escape a string for safe use in a SQL + LIKE statement. + + Example: + + class Article + def self.search(term) + where("title LIKE ?", sanitize_sql_like(term)) + end + end + + Article.search("20% _reduction_") + # => Query looks like "... title LIKE '20\% \_reduction\_' ..." + + *Rob Gilson*, *Yves Senn* + +* Do not quote uuid default value on `change_column`. + + Fixes #14604. + + *Eric Chahin* + +* The comparison between `Relation` and `CollectionProxy` should be consistent. + + Example: + + author.posts == Post.where(author_id: author.id) + # => true + Post.where(author_id: author.id) == author.posts + # => true + + Fixes #13506. + + *Lauro Caetano* + +* Calling `delete_all` on an unloaded `CollectionProxy` no longer + generates a SQL statement containing each id of the collection: + + Before: + + DELETE FROM `model` WHERE `model`.`parent_id` = 1 + AND `model`.`id` IN (1, 2, 3...) + + After: + + DELETE FROM `model` WHERE `model`.`parent_id` = 1 + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select` + which created invalid SQL. + + Fixes #13648. + + *Simon Woker* + +* PostgreSQL adapter only warns once for every missing OID per connection. + + Fixes #14275. + + *Matthew Draper*, *Yves Senn* + +* PostgreSQL adapter automatically reloads it's type map when encountering + unknown OIDs. + + Fixes #14678. + + *Matthew Draper*, *Yves Senn* + +* Fix insertion of records via `has_many :through` association with scope. + + Fixes #3548. + + *Ivan Antropov* + +* Auto-generate stable fixture UUIDs on PostgreSQL. + + Fixes: #11524 + + *Roderick van Domburg* + +* Fixed a problem where an enum would overwrite values of another enum + with the same name in an unrelated class. + + Fixes #14607. + + *Evan Whalen* + +* PostgreSQL and SQLite string columns no longer have a default limit of 255. + + Fixes #13435, #9153. + + *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn* + +* Make possible to have an association called `records`. + + Fixes #11645. + + *prathamesh-sonpatki* + +* `to_sql` on an association now matches the query that is actually executed, where it + could previously have incorrectly accrued additional conditions (e.g. as a result of + a previous query). CollectionProxy now always defers to the association scope's + `arel` method so the (incorrect) inherited one should be entirely concealed. + + Fixes #14003. + + *Jefferson Lai* + +* Block a few default Class methods as scope name. + + For instance, this will raise: + + scope :public, -> { where(status: 1) } + + *arthurnn* + +* Fixed error when using `with_options` with lambda. + + Fixes #9805. + + *Lauro Caetano* + +* Switch `sqlite3:///` URLs (which were temporarily + deprecated in 4.1) from relative to absolute. + + If you still want the previous interpretation, you should replace + `sqlite3:///my/path` with `sqlite3:my/path`. + + *Matthew Draper* + +* Treat blank UUID values as `nil`. + + Example: + + Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil> + + *Dmitry Lavrov* + +* Enable support for materialized views on PostgreSQL >= 9.3. + + *Dave Lee* + +* The PostgreSQL adapter supports custom domains. Fixes #14305. + + *Yves Senn* + +* PostgreSQL `Column#type` is now determined through the corresponding OID. + The column types stay the same except for enum columns. They no longer have + `nil` as type but `enum`. + + See #7814. + + *Yves Senn* + +* Fixed error when specifying a non-empty default value on a PostgreSQL array column. + + Fixes #10613. + + *Luke Steensen* + * Make possible to change `record_timestamps` inside Callbacks. *Tieg Zaharia* diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index 20516bba0c..5a84792f45 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -9,6 +9,10 @@ module ActiveRecord @association end + def ==(other) + other == to_a + end + private def exec_queries diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 860e76fa18..4abe2ad0a0 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -4,6 +4,12 @@ require 'active_support/core_ext/module/remove_method' require 'active_record/errors' module ActiveRecord + class AssociationNotFoundError < ConfigurationError #:nodoc: + def initialize(record, association_name) + super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") + end + end + class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(reflection, associated_class = nil) super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") @@ -145,7 +151,7 @@ module ActiveRecord association = association_instance_get(name) if association.nil? - reflection = self.class.reflect_on_association(name) + raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name) association = reflection.association_class.new(self, reflection) association_instance_set(name, association) end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 8272a5584c..1edd4fa3aa 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -31,6 +31,14 @@ module ActiveRecord @updated end + def decrement_counters # :nodoc: + with_cache_name { |name| decrement_counter name } + end + + def increment_counters # :nodoc: + with_cache_name { |name| increment_counter name } + end + private def find_target? @@ -51,13 +59,15 @@ module ActiveRecord end end - def decrement_counters - with_cache_name { |name| decrement_counter name } + def decrement_counter(counter_cache_name) + if foreign_key_present? + klass.decrement_counter(counter_cache_name, target_id) + end end - def decrement_counter counter_cache_name + def increment_counter(counter_cache_name) if foreign_key_present? - klass.decrement_counter(counter_cache_name, target_id) + klass.increment_counter(counter_cache_name, target_id) end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 5ccaa55a32..47cc1f4b34 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder private def self.add_counter_cache_methods(mixin) - return if mixin.method_defined? :belongs_to_counter_cache_after_create + return if mixin.method_defined? :belongs_to_counter_cache_after_update mixin.class_eval do - def belongs_to_counter_cache_after_create(reflection) - if record = send(reflection.name) - cache_column = reflection.counter_cache_column - record.class.increment_counter(cache_column, record.id) - @_after_create_counter_called = true - end - end - - def belongs_to_counter_cache_before_destroy(reflection) - foreign_key = reflection.foreign_key.to_sym - unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key - record = send reflection.name - if record && !self.destroyed? - cache_column = reflection.counter_cache_column - record.class.decrement_counter(cache_column, record.id) - end - end - end - def belongs_to_counter_cache_after_update(reflection) foreign_key = reflection.foreign_key cache_column = reflection.counter_cache_column @@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder def self.add_counter_cache_callbacks(model, reflection) cache_column = reflection.counter_cache_column - model.after_create lambda { |record| - record.belongs_to_counter_cache_after_create(reflection) - } - - model.before_destroy lambda { |record| - record.belongs_to_counter_cache_before_destroy(reflection) - } - model.after_update lambda { |record| record.belongs_to_counter_cache_after_update(reflection) } diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 1f314e0677..803e3ab9ab 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -134,11 +134,11 @@ module ActiveRecord end def create(attributes = {}, &block) - create_record(attributes, &block) + _create_record(attributes, &block) end def create!(attributes = {}, &block) - create_record(attributes, true, &block) + _create_record(attributes, true, &block) end # Add +records+ to this association. Returns +self+ so method calls may @@ -182,11 +182,11 @@ module ActiveRecord # # See delete for more info. def delete_all(dependent = nil) - if dependent.present? && ![:nullify, :delete_all].include?(dependent) + if dependent && ![:nullify, :delete_all].include?(dependent) raise ArgumentError, "Valid values are :nullify or :delete_all" end - dependent = if dependent.present? + dependent = if dependent dependent elsif options[:dependent] == :destroy :delete_all @@ -248,7 +248,7 @@ module ActiveRecord dependent = _options[:dependent] || options[:dependent] if records.first == :all - if loaded? || dependent == :destroy + if (loaded? || dependent == :destroy) && dependent != :delete_all delete_or_destroy(load_target, dependent) else delete_records(:all, dependent) @@ -449,13 +449,13 @@ module ActiveRecord persisted + memory end - def create_record(attributes, raise = false, &block) + def _create_record(attributes, raise = false, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end if attributes.is_a?(Array) - attributes.collect { |attr| create_record(attr, raise, &block) } + attributes.collect { |attr| _create_record(attr, raise, &block) } else transaction do add_to_target(build_record(attributes)) do |record| diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index eba688866c..84c8cfe72b 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -357,7 +357,7 @@ module ActiveRecord # Deletes all the records from the collection. For +has_many+ associations, # the deletion is done according to the strategy specified by the <tt>:dependent</tt> - # option. Returns an array with the deleted records. + # option. # # If no <tt>:dependent</tt> option is given, then it will follow the # default strategy. The default strategy is <tt>:nullify</tt>. This @@ -435,11 +435,6 @@ module ActiveRecord # # ] # # person.pets.delete_all - # # => [ - # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, - # # #<Pet id: 2, name: "Spook", person_id: 1>, - # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> - # # ] # # Pet.find(1, 2, 3) # # => ActiveRecord::RecordNotFound @@ -860,6 +855,10 @@ module ActiveRecord !!@association.include?(record) end + def arel + scope.arel + end + def proxy_association @association end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 3e4b7902c0..aac85a36c8 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -58,7 +58,7 @@ module ActiveRecord # the loaded flag is set to true as well. def count_records count = if has_cached_counter? - owner.send(:read_attribute, cached_counter_attribute_name) + owner.read_attribute cached_counter_attribute_name else scope.count end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 64bc98c642..73baefb8e1 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -18,7 +18,7 @@ module ActiveRecord # SELECT query if you use #length. def size if has_cached_counter? - owner.send(:read_attribute, cached_counter_attribute_name) + owner.read_attribute cached_counter_attribute_name(reflection) elsif loaded? target.size else @@ -83,12 +83,16 @@ module ActiveRecord @through_records[record.object_id] ||= begin ensure_mutable - through_record = through_association.build + through_record = through_association.build through_scope_attributes through_record.send("#{source_reflection.name}=", record) through_record end end + def through_scope_attributes + scope.where_values_hash(through_association.reflection.name.to_s) + end + def save_through_record(record) build_through_record(record).save! ensure diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 83637a0409..31ddf4e0fc 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -140,36 +140,13 @@ module ActiveRecord end def grouped_records(association, records) - reflection_records = records_by_reflection(association, records) - - reflection_records.each_with_object({}) do |(reflection, r_records),h| - h[reflection] = r_records.group_by { |record| - association_klass(reflection, record) - } - end - end - - def records_by_reflection(association, records) - records.group_by do |record| - reflection = record.class.reflect_on_association(association) - - reflection || raise_config_error(record, association) - end - end - - def raise_config_error(record, association) - raise ActiveRecord::ConfigurationError, - "Association named '#{association}' was not found on #{record.class.name}; " \ - "perhaps you misspelled it?" - end - - def association_klass(reflection, record) - if reflection.macro == :belongs_to && reflection.options[:polymorphic] - klass = record.read_attribute(reflection.foreign_type.to_s) - klass && klass.constantize - else - reflection.klass + h = {} + records.each do |record| + assoc = record.association(association) + klasses = h[assoc.reflection] ||= {} + (klasses[assoc.klass] ||= []) << record end + h end class AlreadyLoaded diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 399aff378a..747bb5f1d6 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,11 +18,11 @@ module ActiveRecord end def create(attributes = {}, &block) - create_record(attributes, &block) + _create_record(attributes, &block) end def create!(attributes = {}, &block) - create_record(attributes, true, &block) + _create_record(attributes, true, &block) end def build(attributes = {}) @@ -52,7 +52,7 @@ module ActiveRecord replace(record) end - def create_record(attributes, raise_error = false) + def _create_record(attributes, raise_error = false) record = build_record(attributes) yield(record) if block_given? saved = record.save diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ea48a13ea8..4b1733619a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -29,6 +29,8 @@ module ActiveRecord end } + BLACKLISTED_CLASS_METHODS = %w(private public protected) + class AttributeMethodCache def initialize @module = Module.new @@ -132,7 +134,7 @@ module ActiveRecord # A class method is 'dangerous' if it is already (re)defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'new' is.) def dangerous_class_method?(method_name) - class_method_defined_within?(method_name, Base) + BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base) end def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 8a1b199997..99070f127b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -79,11 +79,11 @@ module ActiveRecord end end - def update_record(*) + def _update_record(*) partial_writes? ? super(keys_for_partial_write) : super end - def create_record(*) + def _create_record(*) partial_writes? ? super(keys_for_partial_write) : super end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 67abbbc2a0..c3466153d6 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -30,7 +30,8 @@ module ActiveRecord # ==== Parameters # # * +attr_name+ - The field name that should be serialized. - # * +class_name+ - Optional, class name that the object type should be equal to. + # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump` + # or a class name that the object type should be equal to. # # ==== Example # @@ -38,13 +39,23 @@ module ActiveRecord # class User < ActiveRecord::Base # serialize :preferences # end - def serialize(attr_name, class_name = Object) + # + # # Serialize preferences using JSON as coder. + # class User < ActiveRecord::Base + # serialize :preferences, JSON + # end + # + # # Serialize preferences as Hash using YAML coder. + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + def serialize(attr_name, class_name_or_coder = Object) include Behavior - coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) } - class_name + coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } + class_name_or_coder else - Coders::YAMLColumn.new(class_name) + Coders::YAMLColumn.new(class_name_or_coder) end # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index e9622ca0c1..f149d8f127 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -35,7 +35,7 @@ module ActiveRecord # # === One-to-one Example # - # class Post + # class Post < ActiveRecord::Base # has_one :author, autosave: true # end # @@ -76,7 +76,7 @@ module ActiveRecord # # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved: # - # class Post + # class Post < ActiveRecord::Base # has_many :comments # :autosave option is not declared # end # @@ -95,20 +95,23 @@ module ActiveRecord # When <tt>:autosave</tt> is true all children are saved, no matter whether they # are new records or not: # - # class Post + # class Post < ActiveRecord::Base # has_many :comments, autosave: true # end # # post = Post.create(title: 'ruby rocks') # post.comments.create(body: 'hello world') # post.comments[0].body = 'hi everyone' - # post.save # => saves both post and comment, with 'hi everyone' as body + # post.comments.build(body: "good morning.") + # post.title += "!" + # post.save # => saves both post and comments. # # Destroying one of the associated models as part of the parent's save action # is as simple as marking it for destruction: # - # post.comments.last.mark_for_destruction - # post.comments.last.marked_for_destruction? # => true + # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]> + # post.comments[1].mark_for_destruction + # post.comments[1].marked_for_destruction? # => true # post.comments.length # => 2 # # Note that the model is _not_ yet removed from the database: diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 35f19f0bc0..5955673b42 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -302,11 +302,11 @@ module ActiveRecord run_callbacks(:save) { super } end - def create_record #:nodoc: + def _create_record #:nodoc: run_callbacks(:create) { super } end - def update_record(*) #:nodoc: + def _update_record(*) #:nodoc: run_callbacks(:update) { super } end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index da25e640c1..47f2ad9b10 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -381,7 +381,7 @@ module ActiveRecord end def binds_from_relation(relation, binds) - if relation.is_a?(Relation) && binds.blank? + if relation.is_a?(Relation) && binds.empty? relation, binds = relation.arel, relation.bind_values end [relation, binds] diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index a51691bfa8..47fe501752 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -13,7 +13,7 @@ module ActiveRecord end def visit_AddColumn(o) - sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) sql = "ADD #{quote_column_name(o.name)} #{sql_type}" add_column_options!(sql, column_options(o)) end @@ -26,7 +26,7 @@ module ActiveRecord end def visit_ColumnDefinition(o) - sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) column_sql = "#{quote_column_name(o.name)} #{sql_type}" add_column_options!(column_sql, column_options(o)) unless o.primary_key? column_sql @@ -64,7 +64,7 @@ module ActiveRecord end def add_column_options!(sql, options) - sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options) + sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options) # must explicitly check for :null to allow change_column to work on migrations if options[:null] == false sql << " NOT NULL" @@ -75,6 +75,12 @@ module ActiveRecord sql end + def quote_value(value, column) + column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) + + @conn.quote(value, column) + end + def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index c39bf15e83..71c3a4378b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -15,7 +15,7 @@ module ActiveRecord # are typically created by methods in TableDefinition, and added to the # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. - class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc: + class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type) #:nodoc: def primary_key? primary_key || type.to_sym == :primary_key diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index e4c6502bb7..d2ef83b047 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -30,7 +30,7 @@ module ActiveRecord options = o.options sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale]) change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}" - add_column_options!(change_column_sql, options) + add_column_options!(change_column_sql, options.merge(column: column)) add_column_position!(change_column_sql, options) end @@ -187,7 +187,6 @@ module ActiveRecord include Arel::Visitors::BindVisitor end - # FIXME: Make the first parameter more similar for the two adapters def initialize(connection, logger, connection_options, config) super(connection, logger) @connection_options, @config = connection_options, config diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 41af636ef6..b79d1a4458 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -33,9 +33,14 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = URI.parse(url) - @adapter = @uri.scheme + @adapter = @uri.scheme.gsub('-', '_') @adapter = "postgresql" if @adapter == "postgres" - @query = @uri.query || '' + + if @uri.opaque + @uri.opaque, @query = @uri.opaque.split('?', 2) + else + @query = @uri.query + end end # Converts the given URL to a full connection hash. @@ -65,30 +70,38 @@ module ActiveRecord # "localhost" # # => {} def query_hash - Hash[@query.split("&").map { |pair| pair.split("=") }] + Hash[(@query || '').split("&").map { |pair| pair.split("=") }] end def raw_config - query_hash.merge({ - "adapter" => @adapter, - "username" => uri.user, - "password" => uri.password, - "port" => uri.port, - "database" => database, - "host" => uri.host }) + if uri.opaque + query_hash.merge({ + "adapter" => @adapter, + "database" => uri.opaque }) + else + query_hash.merge({ + "adapter" => @adapter, + "username" => uri.user, + "password" => uri.password, + "port" => uri.port, + "database" => database_from_path, + "host" => uri.host }) + end end # Returns name of the database. - # Sqlite3 expects this to be a full path or `:memory:`. - def database + def database_from_path if @adapter == 'sqlite3' - if '/:memory:' == uri.path - ':memory:' - else - uri.path - end + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". + + uri.path else - uri.path.sub(%r{^/},"") + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. + + uri.path.sub(%r{^/}, "") end end end @@ -124,7 +137,7 @@ module ActiveRecord if config resolve_connection config elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call - resolve_env_connection env.to_sym + resolve_symbol_connection env.to_sym else raise AdapterNotSpecified end @@ -193,42 +206,41 @@ module ActiveRecord # def resolve_connection(spec) case spec - when Symbol, String - resolve_env_connection spec + when Symbol + resolve_symbol_connection spec + when String + resolve_string_connection spec when Hash resolve_hash_connection spec end end + def resolve_string_connection(spec) + # Rails has historically accepted a string to mean either + # an environment key or a URL spec, so we have deprecated + # this ambiguous behaviour and in the future this function + # can be removed in favor of resolve_url_connection. + if configurations.key?(spec) || spec !~ /:/ + ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ + "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" + resolve_symbol_connection(spec) + else + resolve_url_connection(spec) + end + end + # Takes the environment such as `:production` or `:development`. # This requires that the @configurations was initialized with a key that # matches. # - # - # Resolver.new("production" => {}).resolve_env_connection(:production) + # Resolver.new("production" => {}).resolve_symbol_connection(:production) # # => {} # - # Takes a connection URL. - # - # Resolver.new({}).resolve_env_connection("postgresql://localhost/foo") - # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } - # - def resolve_env_connection(spec) - # Rails has historically accepted a string to mean either - # an environment key or a URL spec, so we have deprecated - # this ambiguous behaviour and in the future this function - # can be removed in favor of resolve_string_connection and - # resolve_symbol_connection. + def resolve_symbol_connection(spec) if config = configurations[spec.to_s] - if spec.is_a?(String) - ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ - "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" - end resolve_connection(config) - elsif spec.is_a?(String) - resolve_string_connection(spec) else - raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}") + raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") end end @@ -244,7 +256,12 @@ module ActiveRecord spec end - def resolve_string_connection(url) + # Takes a connection URL. + # + # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve_url_connection(url) ConnectionUrlResolver.new(url).to_hash end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 2b5049f5a5..5e82fdcbe0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -20,9 +20,9 @@ module ActiveRecord ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) rescue Mysql2::Error => error if error.message.include?("Unknown database") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 49f0bfbcde..e6aa2ba921 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -36,9 +36,9 @@ module ActiveRecord ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config) rescue Mysql::Error => error if error.message.include?("Unknown database") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb new file mode 100644 index 0000000000..2cbcd5fd50 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -0,0 +1,158 @@ +module ActiveRecord + module ConnectionAdapters + # PostgreSQL-specific extensions to column definitions in a table. + class PostgreSQLColumn < Column #:nodoc: + attr_accessor :array + + def initialize(name, default, oid_type, sql_type = nil, null = true) + @oid_type = oid_type + default_value = self.class.extract_value_from_default(default) + + if sql_type =~ /\[\]$/ + @array = true + super(name, default_value, sql_type[0..sql_type.length - 3], null) + else + @array = false + super(name, default_value, sql_type, null) + end + + @default_function = default if has_default_function?(default_value, default) + end + + def number? + !array && super + end + + def text? + !array && super + end + + # :stopdoc: + class << self + include ConnectionAdapters::PostgreSQLColumn::Cast + include ConnectionAdapters::PostgreSQLColumn::ArrayParser + attr_accessor :money_precision + end + # :startdoc: + + # Extracts the value from a PostgreSQL column default definition. + def self.extract_value_from_default(default) + # This is a performance optimization for Ruby 1.9.2 in development. + # If the value is nil, we return nil straight away without checking + # the regular expressions. If we check each regular expression, + # Regexp#=== will call NilClass#to_str, which will trigger + # method_missing (defined by whiny nil in ActiveSupport) which + # makes this method very very slow. + return default unless default + + case default + when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m + $1 + # Numeric types + when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ + $1 + # Character types + when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m + $1.gsub(/''/, "'") + # Binary data types + when /\A'(.*)'::bytea\z/m + $1 + # Date/time types + when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ + $1 + when /\A'(.*)'::interval\z/ + $1 + # Boolean type + when 'true' + true + when 'false' + false + # Geometric types + when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ + $1 + # Network address types + when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ + $1 + # Bit string types + when /\AB'(.*)'::"?bit(?: varying)?"?\z/ + $1 + # XML type + when /\A'(.*)'::xml\z/m + $1 + # Arrays + when /\A'(.*)'::"?\D+"?\[\]\z/ + $1 + # Hstore + when /\A'(.*)'::hstore\z/ + $1 + # JSON + when /\A'(.*)'::json\z/ + $1 + # Object identifier types + when /\A-?\d+\z/ + $1 + else + # Anything else is blank, some user type, or some function + # and we can't know the value of that, so return nil. + nil + end + end + + def type_cast_for_write(value) + if @oid_type.respond_to?(:type_cast_for_write) + @oid_type.type_cast_for_write(value) + else + super + end + end + + def type_cast(value) + return if value.nil? + return super if encoded? + + @oid_type.type_cast value + end + + def accessor + @oid_type.accessor + end + + private + + def has_default_function?(default_value, default) + !default_value && (%r{\w+\(.*\)} === default) + end + + def extract_limit(sql_type) + case sql_type + when /^bigint/i; 8 + when /^smallint/i; 2 + when /^timestamp/i; nil + else super + end + end + + # Extracts the scale from PostgreSQL-specific data types. + def extract_scale(sql_type) + # Money type has a fixed scale of 2. + sql_type =~ /^money/ ? 2 : super + end + + # Extracts the precision from PostgreSQL-specific data types. + def extract_precision(sql_type) + if sql_type == 'money' + self.class.money_precision + elsif sql_type =~ /timestamp/i + $1.to_i if sql_type =~ /\((\d+)\)/ + else + super + end + end + + # Maps PostgreSQL-specific data types to logical Rails types. + def simplified_type(field_type) + @oid_type.simplified_type(field_type) || super + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 51ee2829b2..168b08ba75 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -44,10 +44,32 @@ module ActiveRecord end end + def select_value(arel, name = nil, binds = []) + arel, binds = binds_from_relation arel, binds + sql = to_sql(arel, binds) + execute_and_clear(sql, name, binds) do |result| + result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0 + end + end + + def select_values(arel, name = nil) + arel, binds = binds_from_relation arel, [] + sql = to_sql(arel, binds) + execute_and_clear(sql, name, binds) do |result| + if result.nfields > 0 + result.column_values(0) + else + [] + end + end + end + # Executes a SELECT query and returns an array of rows. Each row is an # array of field values. def select_rows(sql, name = nil, binds = []) - exec_query(sql, name, binds).rows + execute_and_clear(sql, name, binds) do |result| + result.values + end end # Executes an INSERT query and returns the new record's ID @@ -134,31 +156,20 @@ module ActiveRecord end def exec_query(sql, name = 'SQL', binds = []) - result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : - exec_cache(sql, name, binds) - - types = {} - fields = result.fields - fields.each_with_index do |fname, i| - ftype = result.ftype i - fmod = result.fmod i - types[fname] = type_map.fetch(ftype, fmod) { |oid, mod| - warn "unknown OID: #{fname}(#{oid}) (#{sql})" - OID::Identity.new - } + execute_and_clear(sql, name, binds) do |result| + types = {} + fields = result.fields + fields.each_with_index do |fname, i| + ftype = result.ftype i + fmod = result.fmod i + types[fname] = get_oid_type(ftype, fmod, fname) + end + ActiveRecord::Result.new(fields, result.values, types) end - - ret = ActiveRecord::Result.new(fields, result.values, types) - result.clear - return ret end def exec_delete(sql, name = 'SQL', binds = []) - result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : - exec_cache(sql, name, binds) - affected = result.cmd_tuples - result.clear - affected + execute_and_clear(sql, name, binds) {|result| result.cmd_tuples } end alias :exec_update :exec_delete diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 5d32aaed50..9e898015a6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -6,6 +6,7 @@ module ActiveRecord module OID class Type def type; end + def simplified_type(sql_type); type end def infinity(options = {}) ::Float::INFINITY * (options[:negative] ? -1 : 1) @@ -18,7 +19,9 @@ module ActiveRecord end end - class Text < Type + class String < Type + def type; :string end + def type_cast(value) return if value.nil? @@ -26,9 +29,23 @@ module ActiveRecord end end + class SpecializedString < OID::String + def type; @type end + + def initialize(type) + @type = type + end + end + + class Text < OID::String + def type; :text end + end + class Bit < Type + def type; :string end + def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_bit value else value @@ -37,6 +54,8 @@ module ActiveRecord end class Bytea < Type + def type; :binary end + def type_cast(value) return if value.nil? PGconn.unescape_bytea value @@ -44,9 +63,11 @@ module ActiveRecord end class Money < Type + def type; :decimal end + def type_cast(value) return if value.nil? - return value unless String === value + return value unless ::String === value # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): @@ -88,8 +109,10 @@ module ActiveRecord end class Point < Type + def type; :string end + def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_point value else value @@ -98,13 +121,15 @@ module ActiveRecord end class Array < Type + def type; @subtype.type end + attr_reader :subtype def initialize(subtype) @subtype = subtype end def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype else value @@ -114,6 +139,8 @@ module ActiveRecord class Range < Type attr_reader :subtype + def simplified_type(sql_type); sql_type.to_sym end + def initialize(subtype) @subtype = subtype end @@ -160,6 +187,8 @@ This is not reliable and will be removed in the future. end class Integer < Type + def type; :integer end + def type_cast(value) return if value.nil? @@ -168,6 +197,8 @@ This is not reliable and will be removed in the future. end class Boolean < Type + def type; :boolean end + def type_cast(value) return if value.nil? @@ -177,6 +208,14 @@ This is not reliable and will be removed in the future. class Timestamp < Type def type; :timestamp; end + def simplified_type(sql_type) + case sql_type + when /^timestamp with(?:out)? time zone$/ + :datetime + else + :timestamp + end + end def type_cast(value) return if value.nil? @@ -188,7 +227,7 @@ This is not reliable and will be removed in the future. end class Date < Type - def type; :datetime; end + def type; :date; end def type_cast(value) return if value.nil? @@ -200,6 +239,8 @@ This is not reliable and will be removed in the future. end class Time < Type + def type; :time end + def type_cast(value) return if value.nil? @@ -210,6 +251,8 @@ This is not reliable and will be removed in the future. end class Float < Type + def type; :float end + def type_cast(value) return if value.nil? @@ -218,6 +261,8 @@ This is not reliable and will be removed in the future. end class Decimal < Type + def type; :decimal end + def type_cast(value) return if value.nil? @@ -230,12 +275,16 @@ This is not reliable and will be removed in the future. end class Enum < Type + def type; :enum end + def type_cast(value) value.to_s end end class Hstore < Type + def type; :hstore end + def type_cast_for_write(value) ConnectionAdapters::PostgreSQLColumn.hstore_to_string value end @@ -252,14 +301,20 @@ This is not reliable and will be removed in the future. end class Cidr < Type + def type; :cidr end def type_cast(value) return if value.nil? ConnectionAdapters::PostgreSQLColumn.string_to_cidr value end end + class Inet < Cidr + def type; :inet end + end class Json < Type + def type; :json end + def type_cast_for_write(value) ConnectionAdapters::PostgreSQLColumn.json_to_string value end @@ -275,6 +330,13 @@ This is not reliable and will be removed in the future. end end + class Uuid < Type + def type; :uuid end + def type_cast(value) + value.presence + end + end + class TypeMap def initialize @mapping = {} @@ -321,7 +383,7 @@ This is not reliable and will be removed in the future. } # Register an OID type named +name+ with a typecasting object in - # +type+. +name+ should correspond to the `typname` column in + # +type+. +name+ should correspond to the `typname` column in # the `pg_type` table. def self.register_type(name, type) NAMES[name] = type @@ -338,48 +400,46 @@ This is not reliable and will be removed in the future. end register_type 'int2', OID::Integer.new - alias_type 'int4', 'int2' - alias_type 'int8', 'int2' - alias_type 'oid', 'int2' - + alias_type 'int4', 'int2' + alias_type 'int8', 'int2' + alias_type 'oid', 'int2' register_type 'numeric', OID::Decimal.new + register_type 'float4', OID::Float.new + alias_type 'float8', 'float4' register_type 'text', OID::Text.new - alias_type 'varchar', 'text' - alias_type 'char', 'text' - alias_type 'bpchar', 'text' - alias_type 'xml', 'text' - - # FIXME: why are we keeping these types as strings? - alias_type 'tsvector', 'text' - alias_type 'interval', 'text' - alias_type 'macaddr', 'text' - alias_type 'uuid', 'text' - - register_type 'money', OID::Money.new - register_type 'bytea', OID::Bytea.new + register_type 'varchar', OID::String.new + alias_type 'char', 'varchar' + alias_type 'bpchar', 'varchar' register_type 'bool', OID::Boolean.new register_type 'bit', OID::Bit.new - register_type 'varbit', OID::Bit.new - - register_type 'float4', OID::Float.new - alias_type 'float8', 'float4' - + alias_type 'varbit', 'bit' register_type 'timestamp', OID::Timestamp.new - register_type 'timestamptz', OID::Timestamp.new + alias_type 'timestamptz', 'timestamp' register_type 'date', OID::Date.new register_type 'time', OID::Time.new - register_type 'path', OID::Text.new + register_type 'money', OID::Money.new + register_type 'bytea', OID::Bytea.new register_type 'point', OID::Point.new - register_type 'polygon', OID::Text.new - register_type 'circle', OID::Text.new register_type 'hstore', OID::Hstore.new register_type 'json', OID::Json.new - register_type 'citext', OID::Text.new - register_type 'ltree', OID::Text.new - register_type 'cidr', OID::Cidr.new - alias_type 'inet', 'cidr' + register_type 'inet', OID::Inet.new + register_type 'uuid', OID::Uuid.new + register_type 'xml', SpecializedString.new(:xml) + register_type 'tsvector', SpecializedString.new(:tsvector) + register_type 'macaddr', SpecializedString.new(:macaddr) + register_type 'citext', SpecializedString.new(:citext) + register_type 'ltree', SpecializedString.new(:ltree) + + # FIXME: why are we keeping these types as strings? + alias_type 'interval', 'varchar' + alias_type 'path', 'varchar' + alias_type 'line', 'varchar' + alias_type 'polygon', 'varchar' + alias_type 'circle', 'varchar' + alias_type 'lseg', 'varchar' + alias_type 'box', 'varchar' end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 210172cf32..403e37fde9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -4,14 +4,14 @@ module ActiveRecord module Quoting # Escapes binary strings for bytea input to the database. def escape_bytea(value) - PGconn.escape_bytea(value) if value + @connection.escape_bytea(value) if value end # Unescapes bytea output from a database to the binary string it represents. # NOTE: This is NOT an inverse of escape_bytea! This is only to be used # on escaped binary output from database drive. def unescape_bytea(value) - PGconn.unescape_bytea(value) if value + @connection.unescape_bytea(value) if value end # Quotes PostgreSQL-specific data types for SQL input. @@ -182,6 +182,15 @@ module ActiveRecord end result end + + # Does not quote function default values for UUID columns + def quote_default_value(value, column) #:nodoc: + if column.type == :uuid && value =~ /\(\)/ + value + else + quote(value) + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index e0afa989cd..1dc7a6f0fd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -104,14 +104,11 @@ module ActiveRecord schema, table = Utils.extract_schema_and_table(name.to_s) return false unless table - binds = [[nil, table]] - binds << [nil, schema] if schema - exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind in ('v','r') + WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view AND c.relname = '#{table.gsub(/(^"|"$)/,'')}' AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'} SQL @@ -185,13 +182,15 @@ module ActiveRecord def columns(table_name) # Limit, precision, and scale are all handled by the superclass. column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| - oid = type_map.fetch(oid.to_i, fmod.to_i) { - OID::Identity.new - } + oid = get_oid_type(oid.to_i, fmod.to_i, column_name) PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f') end end + def column_for(table_name, column_name) #:nodoc: + columns(table_name).detect { |c| c.name == column_name.to_s } + end + # Returns the current database name. def current_database query('select current_database()', 'SCHEMA')[0][0] @@ -409,13 +408,15 @@ module ActiveRecord # Changes the default value of a table column. def change_column_default(table_name, column_name, default) clear_cache! - execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}" + column = column_for(table_name, column_name) + execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column end def change_column_null(table_name, column_name, null, default = nil) clear_cache! unless null || default.nil? - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + column = column_for(table_name, column_name) + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column end execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL") end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bcad9f30d7..1f1bd342d1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -7,6 +7,7 @@ require 'active_record/connection_adapters/postgresql/quoting' require 'active_record/connection_adapters/postgresql/schema_statements' require 'active_record/connection_adapters/postgresql/database_statements' require 'active_record/connection_adapters/postgresql/referential_integrity' +require 'active_record/connection_adapters/postgresql/column' require 'arel/visitors/bind_visitor' # Make sure we're using pg high enough for PGResult#values @@ -43,224 +44,6 @@ module ActiveRecord end module ConnectionAdapters - # PostgreSQL-specific extensions to column definitions in a table. - class PostgreSQLColumn < Column #:nodoc: - attr_accessor :array - - def initialize(name, default, oid_type, sql_type = nil, null = true) - @oid_type = oid_type - default_value = self.class.extract_value_from_default(default) - - if sql_type =~ /\[\]$/ - @array = true - super(name, default_value, sql_type[0..sql_type.length - 3], null) - else - @array = false - super(name, default_value, sql_type, null) - end - - @default_function = default if has_default_function?(default_value, default) - end - - def number? - !array && super - end - - def text? - !array && super - end - - # :stopdoc: - class << self - include ConnectionAdapters::PostgreSQLColumn::Cast - include ConnectionAdapters::PostgreSQLColumn::ArrayParser - attr_accessor :money_precision - end - # :startdoc: - - # Extracts the value from a PostgreSQL column default definition. - def self.extract_value_from_default(default) - # This is a performance optimization for Ruby 1.9.2 in development. - # If the value is nil, we return nil straight away without checking - # the regular expressions. If we check each regular expression, - # Regexp#=== will call NilClass#to_str, which will trigger - # method_missing (defined by whiny nil in ActiveSupport) which - # makes this method very very slow. - return default unless default - - case default - when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m - $1 - # Numeric types - when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ - $1 - # Character types - when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m - $1.gsub(/''/, "'") - # Binary data types - when /\A'(.*)'::bytea\z/m - $1 - # Date/time types - when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ - $1 - when /\A'(.*)'::interval\z/ - $1 - # Boolean type - when 'true' - true - when 'false' - false - # Geometric types - when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ - $1 - # Network address types - when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ - $1 - # Bit string types - when /\AB'(.*)'::"?bit(?: varying)?"?\z/ - $1 - # XML type - when /\A'(.*)'::xml\z/m - $1 - # Arrays - when /\A'(.*)'::"?\D+"?\[\]\z/ - $1 - # Hstore - when /\A'(.*)'::hstore\z/ - $1 - # JSON - when /\A'(.*)'::json\z/ - $1 - # Object identifier types - when /\A-?\d+\z/ - $1 - else - # Anything else is blank, some user type, or some function - # and we can't know the value of that, so return nil. - nil - end - end - - def type_cast_for_write(value) - if @oid_type.respond_to?(:type_cast_for_write) - @oid_type.type_cast_for_write(value) - else - super - end - end - - def type_cast(value) - return if value.nil? - return super if encoded? - - @oid_type.type_cast value - end - - def accessor - @oid_type.accessor - end - - private - - def has_default_function?(default_value, default) - !default_value && (%r{\w+\(.*\)} === default) - end - - def extract_limit(sql_type) - case sql_type - when /^bigint/i; 8 - when /^smallint/i; 2 - when /^timestamp/i; nil - else super - end - end - - # Extracts the scale from PostgreSQL-specific data types. - def extract_scale(sql_type) - # Money type has a fixed scale of 2. - sql_type =~ /^money/ ? 2 : super - end - - # Extracts the precision from PostgreSQL-specific data types. - def extract_precision(sql_type) - if sql_type == 'money' - self.class.money_precision - elsif sql_type =~ /timestamp/i - $1.to_i if sql_type =~ /\((\d+)\)/ - else - super - end - end - - # Maps PostgreSQL-specific data types to logical Rails types. - def simplified_type(field_type) - case field_type - # Numeric and monetary types - when /^(?:real|double precision)$/ - :float - # Monetary types - when 'money' - :decimal - when 'hstore' - :hstore - when 'ltree' - :ltree - # Network address types - when 'inet' - :inet - when 'cidr' - :cidr - when 'macaddr' - :macaddr - # Character types - when /^(?:character varying|bpchar)(?:\(\d+\))?$/ - :string - when /^citext(?:\(\d+\))?$/ - :citext - # Binary data types - when 'bytea' - :binary - # Date/time types - when /^timestamp with(?:out)? time zone$/ - :datetime - when /^interval(?:|\(\d+\))$/ - :string - # Geometric types - when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ - :string - # Bit strings - when /^bit(?: varying)?(?:\(\d+\))?$/ - :string - # XML type - when 'xml' - :xml - # tsvector type - when 'tsvector' - :tsvector - # Arrays - when /^\D+\[\]$/ - :string - # Object identifier types - when 'oid' - :integer - # UUID type - when 'uuid' - :uuid - # JSON type - when 'json' - :json - # Small and big integer types - when /^(?:small|big)int$/ - :integer - when /(num|date|tstz|ts|int4|int8)range$/ - field_type.to_sym - # Pass through all types that are not specific to PostgreSQL. - else - super - end - end - end - # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver. # # Options: @@ -426,7 +209,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", - string: { name: "character varying", limit: 255 }, + string: { name: "character varying" }, text: { name: "text" }, integer: { name: "integer" }, float: { name: "float" }, @@ -671,6 +454,10 @@ module ActiveRecord postgresql_version >= 90200 end + def supports_materialized_views? + postgresql_version >= 90300 + end + def enable_extension(name) exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { reload_type_map @@ -772,6 +559,17 @@ module ActiveRecord @type_map end + def get_oid_type(oid, fmod, column_name) + if !type_map.key?(oid) + initialize_type_map(type_map, [oid]) + end + + type_map.fetch(oid, fmod) { + warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." + type_map[oid] = OID::Identity.new + } + end + def reload_type_map type_map.clear initialize_type_map(type_map) @@ -796,25 +594,32 @@ module ActiveRecord type_map end - def initialize_type_map(type_map) + def initialize_type_map(type_map, oids = nil) if supports_ranges? - result = execute(<<-SQL, 'SCHEMA') - SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype + query = <<-SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype FROM pg_type as t LEFT JOIN pg_range as r ON oid = rngtypid SQL else - result = execute(<<-SQL, 'SCHEMA') - SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput + query = <<-SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype FROM pg_type as t SQL end - ranges, nodes = result.partition { |row| row['typinput'] == 'range_in' } - leaves, nodes = nodes.partition { |row| row['typelem'] == '0' } + + if oids + query += "WHERE t.oid::integer IN (%s)" % oids.join(", ") + end + + result = execute(query, 'SCHEMA') + ranges, nodes = result.partition { |row| row['typtype'] == 'r' } + enums, nodes = nodes.partition { |row| row['typtype'] == 'e' } + domains, nodes = nodes.partition { |row| row['typtype'] == 'd' } arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' } + leaves, nodes = nodes.partition { |row| row['typelem'] == '0' } # populate the enum types - enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' } enums.each do |row| type_map[row['oid'].to_i] = OID::Enum.new end @@ -843,10 +648,28 @@ module ActiveRecord range = OID::Range.new subtype type_map[row['oid'].to_i] = range end + + # populate domain types + domains.each do |row| + base_type_oid = row["typbasetype"].to_i + if base_type = type_map[base_type_oid] + type_map[row['oid'].to_i] = base_type + else + warn "unknown base type (OID: #{base_type_oid}) for domain #{row["typname"]}." + end + end end FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: + def execute_and_clear(sql, name, binds) + result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : + exec_cache(sql, name, binds) + ret = yield result + result.clear + ret + end + def exec_no_cache(sql, name, binds) log(sql, name, binds) { @connection.async_exec(sql) } end @@ -924,9 +747,9 @@ module ActiveRecord configure_connection rescue ::PG::Error => error if error.message.include?("does not exist") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 3c5f7a981e..2d5c47967d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -30,12 +30,12 @@ module ActiveRecord db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] - ConnectionAdapters::SQLite3Adapter.new(db, logger, config) + ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) rescue Errno::ENOENT => error if error.message.include?("No such file or directory") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end end @@ -63,7 +63,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL', - string: { name: "varchar", limit: 255 }, + string: { name: "varchar" }, text: { name: "text" }, integer: { name: "integer" }, float: { name: "float" }, @@ -127,7 +127,7 @@ module ActiveRecord include Arel::Visitors::BindVisitor end - def initialize(connection, logger, config) + def initialize(connection, logger, connection_options, config) super(connection, logger) @active = nil diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index bbb866cedf..31e7390bf7 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -58,9 +58,9 @@ module ActiveRecord end class MergeAndResolveDefaultUrlConfig # :nodoc: - def initialize(raw_configurations, url = ENV['DATABASE_URL']) + def initialize(raw_configurations) @raw_config = raw_configurations.dup - @url = url + @env = DEFAULT_ENV.call.to_s end # Returns fully resolved connection hashes. @@ -71,33 +71,10 @@ module ActiveRecord private def config - if @url - raw_merged_into_default - else - @raw_config - end - end - - def raw_merged_into_default - default = default_url_hash - - @raw_config.each do |env, values| - default[env] = values || {} - default[env].merge!("url" => @url) { |h, v1, v2| v1 || v2 } if default[env].is_a?(Hash) - end - default - end - - # When the raw configuration is not present and ENV['DATABASE_URL'] - # is available we return a hash with the connection information in - # the connection URL. This hash responds to any string key with - # resolved connection information. - def default_url_hash - Hash.new do |hash, key| - hash[key] = if key.is_a? String - ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash - else - nil + @raw_config.dup.tap do |cfg| + if url = ENV['DATABASE_URL'] + cfg[@env] ||= {} + cfg[@env]["url"] ||= url end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d9aaf8597f..4e53f66005 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -299,7 +299,7 @@ module ActiveRecord def ==(comparison_object) super || comparison_object.instance_of?(self.class) && - id && + !id.nil? && comparison_object.id == id end alias :eql? :== diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index dcbdf75627..b7b790322a 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -118,5 +118,54 @@ module ActiveRecord update_counters(id, counter_name => -1) end end + + protected + + def actually_destroyed? + @_actually_destroyed + end + + def clear_destroy_state + @_actually_destroyed = nil + end + + private + + def _create_record(*) + id = super + + each_counter_cached_associations do |association| + if send(association.reflection.name) + association.increment_counters + @_after_create_counter_called = true + end + end + + id + end + + def destroy_row + affected_rows = super + + if affected_rows > 0 + each_counter_cached_associations do |association| + foreign_key = association.reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + if send(association.reflection.name) + association.decrement_counters + end + end + end + end + + affected_rows + end + + def each_counter_cached_associations + reflections.each do |name, reflection| + yield association(name) if reflection.belongs_to? && reflection.counter_cache_column + end + end + end end diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 4aa323fb00..18f1ca26de 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/deep_dup' + module ActiveRecord # Declare an enum attribute where the values map to integers in the database, # but can be queried by name. Example: @@ -65,10 +67,14 @@ module ActiveRecord # # Where conditions on an enum attribute must use the ordinal value of an enum. module Enum - DEFINED_ENUMS = {} # :nodoc: + def self.extended(base) + base.class_attribute(:defined_enums) + base.defined_enums = {} + end - def enum_mapping_for(attr_name) # :nodoc: - DEFINED_ENUMS[attr_name.to_s] + def inherited(base) + base.defined_enums = defined_enums.deep_dup + super end def enum(definitions) @@ -122,9 +128,8 @@ module ActiveRecord klass.send(:detect_enum_conflict!, name, value, true) klass.scope value, -> { klass.where name => i } end - - DEFINED_ENUMS[name.to_s] = enum_values end + defined_enums[name.to_s] = enum_values end end @@ -134,7 +139,7 @@ module ActiveRecord mod = Module.new do private def save_changed_attribute(attr_name, value) - if (mapping = self.class.enum_mapping_for(attr_name)) + if (mapping = self.class.defined_enums[attr_name.to_s]) if attribute_changed?(attr_name) old = changed_attributes[attr_name] diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 7f6228131f..71efbb8f93 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -95,15 +95,7 @@ module ActiveRecord end # Raised when a given database does not exist - class NoDatabaseError < ActiveRecordError - def initialize(message) - super extend_message(message) - end - - # can be over written to add additional error information. - def extend_message(message) - message - end + class NoDatabaseError < StatementInvalid end # Raised on attempt to save stale record. Record is stale when it's being saved in another query after diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6f134bbef8..47d32fae05 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -2,6 +2,7 @@ require 'erb' require 'yaml' require 'zlib' require 'active_support/dependencies' +require 'active_support/core_ext/securerandom' require 'active_record/fixture_set/file' require 'active_record/errors' @@ -550,9 +551,13 @@ module ActiveRecord end # Returns a consistent, platform-independent identifier for +label+. - # Identifiers are positive integers less than 2^30. - def self.identify(label) - Zlib.crc32(label.to_s) % MAX_ID + # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes. + def self.identify(label, column_type = :integer) + if column_type == :uuid + SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, label.to_s) + else + Zlib.crc32(label.to_s) % MAX_ID + end end # Superclass for the evaluation contexts used by ERB fixtures. @@ -633,7 +638,7 @@ module ActiveRecord # generate a primary key if necessary if has_primary_key_column? && !row.include?(primary_key_name) - row[primary_key_name] = ActiveRecord::FixtureSet.identify(label) + row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type) end # If STI is used, find the correct subclass for association reflection @@ -656,7 +661,8 @@ module ActiveRecord row[association.foreign_type] = $1 end - row[fk_name] = ActiveRecord::FixtureSet.identify(value) + fk_type = association.active_record.columns_hash[association.foreign_key].type + row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) end when :has_many if association.options[:through] @@ -683,6 +689,10 @@ module ActiveRecord def name @association.name end + + def primary_key_type + @association.klass.column_types[@association.klass.primary_key].type + end end class HasManyThroughProxy < ReflectionProxy # :nodoc: @@ -700,17 +710,22 @@ module ActiveRecord @primary_key_name ||= model_class && model_class.primary_key end + def primary_key_type + @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type + end + def add_join_records(rows, row, association) # This is the case when the join table has no fixtures file if (targets = row.delete(association.name.to_s)) - table_name = association.join_table - lhs_key = association.lhs_key - rhs_key = association.rhs_key + table_name = association.join_table + column_type = association.primary_key_type + lhs_key = association.lhs_key + rhs_key = association.rhs_key targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) rows[table_name].concat targets.map { |target| { lhs_key => row[primary_key_name], - rhs_key => ActiveRecord::FixtureSet.identify(target) } + rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } } end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 6f54729b3c..4d63b04d9f 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,7 +66,7 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end - def update_record(attribute_names = @attributes.keys) #:nodoc: + def _update_record(attribute_names = @attributes.keys) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 9d92e747d4..e6195e48a5 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -485,10 +485,10 @@ module ActiveRecord end # Takes in a limit and checks if the attributes_collection has too many - # records. The method will take limits in the form of symbols, procs, and - # number-like objects (anything that can be compared with an integer). + # records. It accepts limit in the form of symbol, proc, or + # number-like object (anything that can be compared with an integer). # - # Will raise an TooManyRecords error if the attributes_collection is + # Raises TooManyRecords error if the attributes_collection is # larger than the limit. def check_record_limit!(limit, attributes_collection) if limit @@ -519,7 +519,7 @@ module ActiveRecord ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) end - # Determines if a new record should be build by checking for + # Determines if a new record should be rejected by checking # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this # association and evaluates to +true+. def reject_new_record?(association_name, attributes) diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 5b255c3fe5..05d0c41678 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -43,7 +43,7 @@ module ActiveRecord end def count(*) - 0 + calculate :count, nil end def sum(*) @@ -54,7 +54,7 @@ module ActiveRecord # TODO: Remove _options argument as soon we remove support to # activerecord-deprecated_finders. if operation == :count - 0 + group_values.any? ? Hash.new : 0 else nil end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 1a2581f579..13d7432773 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -484,24 +484,24 @@ module ActiveRecord def create_or_update raise ReadOnlyRecord if readonly? - result = new_record? ? create_record : update_record + result = new_record? ? _create_record : _update_record result != false end # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. - def update_record(attribute_names = @attributes.keys) + def _update_record(attribute_names = @attributes.keys) attributes_values = arel_attributes_with_values_for_update(attribute_names) if attributes_values.empty? 0 else - self.class.unscoped.update_record attributes_values, id, id_was + self.class.unscoped._update_record attributes_values, id, id_was end end # Creates a record with values matching those of the instance attributes # and returns its id. - def create_record(attribute_names = @attributes.keys) + def _create_record(attribute_names = @attributes.keys) attributes_values = arel_attributes_with_values_for_create(attribute_names) new_id = self.class.unscoped.insert attributes_values diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 11b564f8f9..a4ceacbf44 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -116,17 +116,22 @@ module ActiveRecord # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do + self.configurations = Rails.application.config.database_configuration - class ActiveRecord::NoDatabaseError - remove_possible_method :extend_message - def extend_message(message) - message << "Run `$ bin/rake db:create db:migrate` to create your database" - message - end - end + begin + establish_connection + rescue ActiveRecord::NoDatabaseError + warn <<-end_warning +Oops - You have a database configured, but it doesn't exist yet! - self.configurations = Rails.application.config.database_configuration - establish_connection +Here's how to get started: + + 1. Configure your database in config/database.yml. + 2. Run `bin/rake db:create` to create the database. + 3. Run `bin/rake db:setup` to load your database schema. +end_warning + raise + end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index bce7766501..4fde6677be 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -22,11 +22,11 @@ module ActiveRecord end def self.add_reflection(ar, name, reflection) - ar.reflections = ar.reflections.merge(name => reflection) + ar.reflections = ar.reflections.merge(name.to_s => reflection) end def self.add_aggregate_reflection(ar, name, reflection) - ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection) + ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection) end # \Reflection enables to interrogate Active Record classes and objects @@ -48,7 +48,7 @@ module ActiveRecord # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection # def reflect_on_aggregation(aggregation) - aggregate_reflections[aggregation] + aggregate_reflections[aggregation.to_s] end # Returns an array of AssociationReflection objects for all the @@ -72,7 +72,7 @@ module ActiveRecord # Invoice.reflect_on_association(:line_items).macro # returns :has_many # def reflect_on_association(association) - reflections[association] + reflections[association.to_s] end # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled. @@ -151,7 +151,7 @@ module ActiveRecord super || other_aggregation.kind_of?(self.class) && name == other_aggregation.name && - other_aggregation.options && + !other_aggregation.options.nil? && active_record == other_aggregation.active_record end @@ -449,9 +449,9 @@ module ActiveRecord end def derive_class_name - class_name = name.to_s.camelize + class_name = name.to_s class_name = class_name.singularize if collection? - class_name + class_name.camelize end def derive_foreign_key @@ -617,7 +617,7 @@ module ActiveRecord # # => [:tag, :tags] # def source_reflection_names - (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq + options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq end def source_reflection_name # :nodoc: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 9eaba4a655..709edbee88 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -70,7 +70,7 @@ module ActiveRecord binds) end - def update_record(values, id, id_was) # :nodoc: + def _update_record(values, id, id_was) # :nodoc: substitutes, binds = substitute_values values um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key) @@ -238,7 +238,7 @@ module ActiveRecord # Returns size of the records. def size - loaded? ? @records.length : count + loaded? ? @records.length : count(:all) end # Returns true if there are no records. @@ -248,8 +248,7 @@ module ActiveRecord if limit_value == 0 true else - # FIXME: This count is not compatible with #select('authors.*') or other select narrows - c = count + c = count(:all) c.respond_to?(:zero?) ? c.zero? : c.empty? end end @@ -529,9 +528,9 @@ module ActiveRecord # # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} - def where_values_hash + def where_values_hash(relation_table_name = table_name) equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node| - node.left.relation.name == table_name + node.left.relation.name == relation_table_name } binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] @@ -570,6 +569,8 @@ module ActiveRecord # Compares two relations for equality. def ==(other) case other + when Associations::CollectionProxy, AssociationRelation + self == other.to_a when Relation other.to_sql == to_sql when Array diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 45ffb99868..514ebc2bfe 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -188,7 +188,7 @@ module ActiveRecord private def has_include?(column_name) - eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?)) + eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?)) end def perform_calculation(operation, column_name, options = {}) @@ -231,7 +231,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = reorder(nil) + relation = unscope(:order) column_alias = column_name diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c2b9dc08fe..7af4b29ebc 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -129,9 +129,9 @@ module ActiveRecord # def first(limit = nil) if limit - find_nth_with_limit(offset_value, limit) + find_nth_with_limit(offset_index, limit) else - find_nth(:first, offset_value) + find_nth(0, offset_index) end end @@ -181,7 +181,7 @@ module ActiveRecord # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) # Person.where(["user_name = :u", { u: user_name }]).second def second - find_nth(:second, offset_value ? offset_value + 1 : 1) + find_nth(1, offset_index) end # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -197,7 +197,7 @@ module ActiveRecord # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) # Person.where(["user_name = :u", { u: user_name }]).third def third - find_nth(:third, offset_value ? offset_value + 2 : 2) + find_nth(2, offset_index) end # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -213,7 +213,7 @@ module ActiveRecord # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) # Person.where(["user_name = :u", { u: user_name }]).fourth def fourth - find_nth(:fourth, offset_value ? offset_value + 3 : 3) + find_nth(3, offset_index) end # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -229,7 +229,7 @@ module ActiveRecord # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) # Person.where(["user_name = :u", { u: user_name }]).fifth def fifth - find_nth(:fifth, offset_value ? offset_value + 4 : 4) + find_nth(4, offset_index) end # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -245,7 +245,7 @@ module ActiveRecord # Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44) # Person.where(["user_name = :u", { u: user_name }]).forty_two def forty_two - find_nth(:forty_two, offset_value ? offset_value + 41 : 41) + find_nth(41, offset_index) end # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -334,6 +334,10 @@ module ActiveRecord private + def offset_index + offset_value || 0 + end + def find_with_associations join_dependency = construct_join_dependency @@ -468,20 +472,24 @@ module ActiveRecord end end - def find_nth(ordinal, offset) + def find_nth(index, offset) if loaded? - @records.send(ordinal) + @records[index] else + offset += index @offsets[offset] ||= find_nth_with_limit(offset, 1).first end end def find_nth_with_limit(offset, limit) - if order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a - else - limit(limit).offset(offset).to_a - end + relation = if order_values.empty? && primary_key + order(arel_table[primary_key].asc) + else + self + end + + relation = relation.offset(offset) unless offset.zero? + relation.limit(limit).to_a end def find_last diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 182b9ed89c..be44fccad5 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -139,7 +139,6 @@ module ActiveRecord def merge_single_values relation.from_value = values[:from] unless relation.from_value relation.lock_value = values[:lock] unless relation.lock_value - relation.reverse_order_value = values[:reverse_order] unless values[:create_with].blank? relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with]) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0213bca981..4287304945 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -825,7 +825,9 @@ module ActiveRecord end def reverse_order! # :nodoc: - self.reverse_order_value = !reverse_order_value + orders = order_values.uniq + orders.reject!(&:blank?) + self.order_values = reverse_sql_order(orders) self end @@ -871,7 +873,6 @@ module ActiveRecord case scope when :order - self.reverse_order_value = false result = [] else result = [] unless single_val_method @@ -1031,7 +1032,6 @@ module ActiveRecord def build_order(arel) orders = order_values.uniq orders.reject!(&:blank?) - orders = reverse_sql_order(orders) if reverse_order_value arel.order(*orders) unless orders.empty? end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 5a71c13d91..be62e41932 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -107,6 +107,13 @@ module ActiveRecord end.join(', ') end + # Sanitizes a +string+ so that it is safe to use within a sql + # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end + # Accepts an array of conditions. The array has each value # sanitized and interpolated into the SQL statement. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 6ce0495f6f..168b338b97 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -28,7 +28,7 @@ module ActiveRecord # Example usage of +DatabaseTasks+ outside Rails could look as such: # # include ActiveRecord::Tasks - # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml')) + # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml') # DatabaseTasks.db_dir = 'db' # # other settings... # diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 7178bed560..6c30ccab72 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -43,7 +43,7 @@ module ActiveRecord private - def create_record + def _create_record if self.record_timestamps current_time = current_time_from_proper_timezone @@ -57,7 +57,7 @@ module ActiveRecord super end - def update_record(*args) + def _update_record(*args) if should_record_timestamps? current_time = current_time_from_proper_timezone diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 71c71cb4b1..ee080451a9 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -93,7 +93,7 @@ module ActiveRecord end def map_enum_attribute(klass, attribute, value) - mapping = klass.enum_mapping_for(attribute.to_s) + mapping = klass.defined_enums[attribute.to_s] value = mapping[value] if value && mapping value end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index ed4d0d503d..90953ce6cd 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -144,9 +144,9 @@ module ActiveRecord @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" end end - - def test_foreign_key_violations_are_translated_to_specific_exception - unless current_adapter?(:SQLite3Adapter) + + unless current_adapter?(:SQLite3Adapter) + def test_foreign_key_violations_are_translated_to_specific_exception assert_raises(ActiveRecord::InvalidForeignKey) do # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method if @connection.prefetch_primary_key? @@ -157,10 +157,8 @@ module ActiveRecord end end end - end - - def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false - unless current_adapter?(:SQLite3Adapter) + + def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false klass_has_fk = Class.new(ActiveRecord::Base) do self.table_name = 'fk_test_has_fk' end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index d1c644c016..7c0f11b033 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -1,10 +1,10 @@ require "cases/helper" +require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + include ConnectionHelper + def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end @@ -12,8 +12,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end teardown do - ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + reset_connection end def test_add_index diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 42ffb91095..412efa22ff 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,7 +1,9 @@ require "cases/helper" +require 'support/connection_helper' require 'support/ddl_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper include DdlHelper class Klass < ActiveRecord::Base @@ -160,15 +162,6 @@ class MysqlConnectionTest < ActiveRecord::TestCase private - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end - def with_example_table(&block) definition ||= <<-SQL `id` int(11) auto_increment PRIMARY KEY, diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 81c614924f..cefc3e3c7e 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,10 +1,10 @@ require "cases/helper" +require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + include ConnectionHelper + def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end @@ -12,8 +12,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end teardown do - ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + reset_connection end def test_add_index diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 98febd2d75..182d9409c7 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,15 +1,18 @@ require "cases/helper" +require 'support/connection_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + def setup super @subscriber = SQLSubscriber.new - ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) @connection = ActiveRecord::Base.connection end def teardown - ActiveSupport::Notifications.unsubscribe(@subscriber) + ActiveSupport::Notifications.unsubscribe(@subscription) super end @@ -103,15 +106,4 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime) end end - - private - - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 714da83a54..18dd4a6de8 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -25,7 +25,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type - assert_equal "character varying(255)", @column.sql_type + assert_equal "character varying", @column.sql_type assert @column.array assert_not @column.text? assert_not @column.number? @@ -48,6 +48,17 @@ class PostgresqlArrayTest < ActiveRecord::TestCase PgArray.reset_column_information end + def test_default_strings + @connection.add_column 'pg_arrays', 'names', :string, array: true, default: ["foo", "bar"] + PgArray.reset_column_information + column = PgArray.columns_hash["names"] + + assert_equal(["foo", "bar"], column.default) + assert_equal(["foo", "bar"], PgArray.new.names) + ensure + PgArray.reset_column_information + end + def test_change_column_with_array @connection.add_column :pg_arrays, :snippets, :string, array: true, default: [] @connection.change_column :pg_arrays, :snippets, :text, array: true, default: "{}" diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index c3394d7712..e3478856c8 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -70,6 +70,23 @@ class PostgresqlByteaTest < ActiveRecord::TestCase assert_equal(data, record.payload) end + def test_via_to_sql + data = "'\u001F\\" + ByteaDataType.create(payload: data) + sql = ByteaDataType.where(payload: data).select(:payload).to_sql + result = @connection.query(sql) + assert_equal([[data]], result) + end + + def test_via_to_sql_with_complicating_connection + Thread.new do + other_conn = ActiveRecord::Base.connection + other_conn.execute('SET standard_conforming_strings = off') + end.join + + test_via_to_sql + end + def test_write_binary data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index aa4996133f..5f84c893c0 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -1,19 +1,22 @@ require "cases/helper" +require 'support/connection_helper' module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + class NonExistentTable < ActiveRecord::Base end def setup super @subscriber = SQLSubscriber.new - ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) @connection = ActiveRecord::Base.connection end def teardown - ActiveSupport::Notifications.unsubscribe(@subscriber) + ActiveSupport::Notifications.unsubscribe(@subscription) super end @@ -198,17 +201,5 @@ module ActiveRecord ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}})) end end - - private - - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end - end end diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb new file mode 100644 index 0000000000..5286a847a4 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'support/connection_helper' +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlDomainTest < ActiveRecord::TestCase + include ConnectionHelper + + class PostgresqlDomain < ActiveRecord::Base + self.table_name = "postgresql_domains" + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)" + @connection.create_table('postgresql_domains') do |t| + t.column :price, :custom_money + end + end + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_domains' + @connection.execute 'DROP DOMAIN IF EXISTS custom_money' + reset_connection + end + + def test_column + column = PostgresqlDomain.columns_hash["price"] + assert_equal :decimal, column.type + assert_equal "custom_money", column.sql_type + assert column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end + + def test_domain_acts_like_basetype + PostgresqlDomain.create price: "" + record = PostgresqlDomain.first + assert_nil record.price + + record.price = "34.15" + record.save! + + assert_equal BigDecimal.new("34.15"), record.reload.price + end +end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 381c397922..4146b117f6 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -1,18 +1,16 @@ # -*- coding: utf-8 -*- require "cases/helper" +require 'support/connection_helper' require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' class PostgresqlEnumTest < ActiveRecord::TestCase + include ConnectionHelper + class PostgresqlEnum < ActiveRecord::Base self.table_name = "postgresql_enums" end - teardown do - @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' - @connection.execute 'DROP TYPE IF EXISTS mood' - end - def setup @connection = ActiveRecord::Base.connection @connection.transaction do @@ -23,14 +21,17 @@ class PostgresqlEnumTest < ActiveRecord::TestCase t.column :current_mood, :mood end end - # reload type map after creating the enum type - @connection.send(:reload_type_map) + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' + @connection.execute 'DROP TYPE IF EXISTS mood' + reset_connection end def test_column column = PostgresqlEnum.columns_hash["current_mood"] - # TODO: enum columns should be of type enum or string, not nil. - assert_nil column.type + assert_equal :enum, column.type assert_equal "mood", column.sql_type assert_not column.number? assert_not column.text? diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 49d8ec238d..b7791078db 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,11 +1,13 @@ # encoding: utf-8 require "cases/helper" require 'support/ddl_helper' +require 'support/connection_helper' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapterTest < ActiveRecord::TestCase include DdlHelper + include ConnectionHelper def setup @connection = ActiveRecord::Base.connection @@ -357,6 +359,43 @@ module ActiveRecord end end + def test_reload_type_map_for_newly_defined_types + @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')" + result = @connection.select_all "SELECT 'good'::feeling" + assert_instance_of(PostgreSQLAdapter::OID::Enum, + result.column_types["feeling"]) + ensure + @connection.execute "DROP TYPE IF EXISTS feeling" + reset_connection + end + + def test_only_reload_type_map_once_for_every_unknown_type + silence_warnings do + assert_queries 2, ignore_none: true do + @connection.select_all "SELECT NULL::anyelement" + end + assert_queries 1, ignore_none: true do + @connection.select_all "SELECT NULL::anyelement" + end + assert_queries 2, ignore_none: true do + @connection.select_all "SELECT NULL::anyarray" + end + end + ensure + reset_connection + end + + def test_only_warn_on_first_encounter_of_unknown_oid + warning = capture(:stderr) { + @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "SELECT NULL::anyelement" + } + assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning) + ensure + reset_connection + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 39f5b9b18a..51846e22d9 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -47,9 +47,9 @@ module ActiveRecord def test_quote_cast_numeric fixnum = 666 - c = PostgreSQLColumn.new(nil, nil, OID::Decimal.new, 'varchar') + c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar') assert_equal "'666'", @conn.quote(fixnum, c) - c = PostgreSQLColumn.new(nil, nil, OID::Decimal.new, 'text') + c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text') assert_equal "'666'", @conn.quote(fixnum, c) end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index a9d4312fb3..060b17d071 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'support/connection_helper' require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' @@ -8,17 +9,13 @@ if ActiveRecord::Base.connection.supports_ranges? end class PostgresqlRangeTest < ActiveRecord::TestCase - teardown do - @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' - @connection.execute 'DROP TYPE IF EXISTS floatrange' - end + self.use_transactional_fixtures = false + include ConnectionHelper def setup @connection = PostgresqlRange.connection begin @connection.transaction do - @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' - @connection.execute 'DROP TYPE IF EXISTS floatrange' @connection.execute <<_SQL CREATE TYPE floatrange AS RANGE ( subtype = float8, @@ -37,7 +34,6 @@ _SQL @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange' end - @connection.send :reload_type_map PostgresqlRange.reset_column_information rescue ActiveRecord::StatementInvalid skip "do not test on PG without range" @@ -96,6 +92,12 @@ _SQL @empty_range = PostgresqlRange.find(105) end + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' + @connection.execute 'DROP TYPE IF EXISTS floatrange' + reset_connection + end + def test_data_type_of_range_types assert_equal :daterange, @first_range.column_for_attribute(:date_range).type assert_equal :numrange, @first_range.column_for_attribute(:num_range).type diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 5e5f653d17..11ec7599a3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -271,13 +271,13 @@ class SchemaTest < ActiveRecord::TestCase end def test_with_uppercase_index_name - ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} + @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" + assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} + @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - ActiveRecord::Base.connection.schema_search_path = SCHEMA_NAME - assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "things_Index"} - ActiveRecord::Base.connection.schema_search_path = "public" + with_schema_search_path SCHEMA_NAME do + assert_nothing_raised { @connection.remove_index! "things", "things_Index"} + end end def test_primary_key_with_schema_specified @@ -328,18 +328,17 @@ class SchemaTest < ActiveRecord::TestCase end def test_prepared_statements_with_multiple_schemas + [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| + with_schema_search_path schema_name do + Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) + end + end - @connection.schema_search_path = SCHEMA_NAME - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) - - @connection.schema_search_path = SCHEMA2_NAME - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now) - - @connection.schema_search_path = SCHEMA_NAME - assert_equal 1, Thing5.count - - @connection.schema_search_path = SCHEMA2_NAME - assert_equal 1, Thing5.count + [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| + with_schema_search_path schema_name do + assert_equal 1, Thing5.count + end + end end def test_schema_exists? diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index f79a7a598b..bdf8e15e3e 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -40,6 +40,19 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase drop_table "uuid_data_type" end + def test_change_column_default + @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()" + UUIDType.reset_column_information + column = UUIDType.columns.find { |c| c.name == 'thingy' } + assert_equal "uuid_generate_v1()", column.default_function + + @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()" + + UUIDType.reset_column_information + column = UUIDType.columns.find { |c| c.name == 'thingy' } + assert_equal "uuid_generate_v4()", column.default_function + end + def test_data_type_of_uuid_types column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type @@ -50,6 +63,11 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase assert_not column.array end + def test_treat_blank_uuid_as_nil + UUIDType.create! guid: '' + assert_equal(nil, UUIDType.last.guid) + end + def test_uuid_formats ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}", diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 08071894c4..47b7d38eda 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -1,11 +1,15 @@ require "cases/helper" -class ViewTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false +module ViewTestConcern + extend ActiveSupport::Concern + + included do + self.use_transactional_fixtures = false + mattr_accessor :view_type + end SCHEMA_NAME = 'test_schema' TABLE_NAME = 'things' - VIEW_NAME = 'view_things' COLUMNS = [ 'id integer', 'name character varying(50)', @@ -14,17 +18,19 @@ class ViewTest < ActiveRecord::TestCase ] class ThingView < ActiveRecord::Base - self.table_name = 'test_schema.view_things' end def setup + super + ThingView.table_name = "#{SCHEMA_NAME}.#{view_type}_things" + @connection = ActiveRecord::Base.connection @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" - @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" - @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" + @connection.execute "CREATE #{view_type.humanize} #{ThingView.table_name} AS SELECT * FROM #{SCHEMA_NAME}.#{TABLE_NAME}" end - teardown do + def teardown + super @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end @@ -35,7 +41,7 @@ class ViewTest < ActiveRecord::TestCase def test_column_definitions assert_nothing_raised do - assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}") + assert_equal COLUMNS, columns(ThingView.table_name) end end @@ -47,3 +53,15 @@ class ViewTest < ActiveRecord::TestCase end end + +class ViewTest < ActiveRecord::TestCase + include ViewTestConcern + self.view_type = 'view' +end + +if ActiveRecord::Base.connection.supports_materialized_views? + class MaterializedViewTest < ActiveRecord::TestCase + include ViewTestConcern + self.view_type = 'materialized_view' + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 73cb739b2b..14aad61ce2 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -2,28 +2,22 @@ require "cases/helper" require 'models/owner' require 'tempfile' +require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class SQLite3AdapterTest < ActiveRecord::TestCase + include DdlHelper + self.use_transactional_fixtures = false class DualEncoding < ActiveRecord::Base end def setup - @conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 - @conn.execute <<-eosql - CREATE TABLE items ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer - ) - eosql - - @subscriber = SQLSubscriber.new - ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @conn = Base.sqlite3_connection database: ':memory:', + adapter: 'sqlite3', + timeout: 100 end def test_bad_connection @@ -37,7 +31,7 @@ module ActiveRecord def test_connect_with_url original_connection = ActiveRecord::Base.remove_connection tf = Tempfile.open 'whatever' - url = "sqlite3://#{tf.path}" + url = "sqlite3:#{tf.path}" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure @@ -48,7 +42,7 @@ module ActiveRecord def test_connect_memory_with_url original_connection = ActiveRecord::Base.remove_connection - url = "sqlite3:///:memory:" + url = "sqlite3::memory:" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure @@ -57,8 +51,10 @@ module ActiveRecord end def test_valid_column - column = @conn.columns('items').find { |col| col.name == 'id' } - assert @conn.valid_type?(column.type) + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end end # sqlite databases should be able to support any type and not @@ -69,13 +65,8 @@ module ActiveRecord assert @conn.valid_type?(:foobar) end - def teardown - ActiveSupport::Notifications.unsubscribe(@subscriber) - super - end - def test_column_types - owner = Owner.create!(:name => "hello".encode('ascii-8bit')) + owner = Owner.create!(name: "hello".encode('ascii-8bit')) owner.reload select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' result = Owner.connection.exec_query <<-esql @@ -85,23 +76,28 @@ module ActiveRecord esql assert(!result.rows.first.include?("blob"), "should not store blobs") + ensure + owner.delete end def test_exec_insert - column = @conn.columns('items').find { |col| col.name == 'number' } - vals = [[column, 10]] - @conn.exec_insert('insert into items (number) VALUES (?)', 'SQL', vals) + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'number' } + vals = [[column, 10]] + @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals) - result = @conn.exec_query( - 'select number from items where number = ?', 'SQL', vals) + result = @conn.exec_query( + 'select number from ex where number = ?', 'SQL', vals) - assert_equal 1, result.rows.length - assert_equal 10, result.rows.first.first + assert_equal 1, result.rows.length + assert_equal 10, result.rows.first.first + end end def test_primary_key_returns_nil_for_no_pk - @conn.exec_query('create table ex(id int, data string)') - assert_nil @conn.primary_key('ex') + with_example_table 'id int, data string' do + assert_nil @conn.primary_key('ex') + end end def test_connection_no_db @@ -112,17 +108,17 @@ module ActiveRecord def test_bad_timeout assert_raises(TypeError) do - Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 'usa' + Base.sqlite3_connection database: ':memory:', + adapter: 'sqlite3', + timeout: 'usa' end end # connection is OK with a nil timeout def test_nil_timeout - conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => nil + conn = Base.sqlite3_connection database: ':memory:', + adapter: 'sqlite3', + timeout: nil assert conn, 'made a connection' end @@ -141,44 +137,47 @@ module ActiveRecord end def test_exec_no_binds - @conn.exec_query('create table ex(id int, data string)') - result = @conn.exec_query('SELECT id, data FROM ex') - assert_equal 0, result.rows.length - assert_equal 2, result.columns.length - assert_equal %w{ id data }, result.columns - - @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec_query('SELECT id, data FROM ex') - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length - - assert_equal [[1, 'foo']], result.rows + with_example_table 'id int, data string' do + result = @conn.exec_query('SELECT id, data FROM ex') + assert_equal 0, result.rows.length + assert_equal 2, result.columns.length + assert_equal %w{ id data }, result.columns + + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @conn.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [[1, 'foo']], result.rows + end end def test_exec_query_with_binds - @conn.exec_query('create table ex(id int, data string)') - @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + with_example_table 'id int, data string' do + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @conn.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end def test_exec_query_typecasts_bind_vals - @conn.exec_query('create table ex(id int, data string)') - @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @conn.columns('ex').find { |col| col.name == 'id' } + with_example_table 'id int, data string' do + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @conn.columns('ex').find { |col| col.name == 'id' } - result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + result = @conn.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end def test_quote_binary_column_escapes_it @@ -190,7 +189,7 @@ module ActiveRecord ) eosql str = "\x80".force_encoding("ASCII-8BIT") - binary = DualEncoding.new :name => 'いただきます!', :data => str + binary = DualEncoding.new name: 'いただきます!', data: str binary.save! assert_equal str, binary.data @@ -202,16 +201,20 @@ module ActiveRecord name = 'hello'.force_encoding(Encoding::ASCII_8BIT) Owner.create(name: name) assert_equal Encoding::ASCII_8BIT, name.encoding + ensure + Owner.delete_all end def test_execute - @conn.execute "INSERT INTO items (number) VALUES (10)" - records = @conn.execute "SELECT * FROM items" - assert_equal 1, records.length - - record = records.first - assert_equal 10, record['number'] - assert_equal 1, record['id'] + with_example_table do + @conn.execute "INSERT INTO ex (number) VALUES (10)" + records = @conn.execute "SELECT * FROM ex" + assert_equal 1, records.length + + record = records.first + assert_equal 10, record['number'] + assert_equal 1, record['id'] + end end def test_quote_string @@ -219,128 +222,141 @@ module ActiveRecord end def test_insert_sql - 2.times do |i| - rv = @conn.insert_sql "INSERT INTO items (number) VALUES (#{i})" - assert_equal(i + 1, rv) + with_example_table do + 2.times do |i| + rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})" + assert_equal(i + 1, rv) + end + + records = @conn.execute "SELECT * FROM ex" + assert_equal 2, records.length end - - records = @conn.execute "SELECT * FROM items" - assert_equal 2, records.length end def test_insert_sql_logged - sql = "INSERT INTO items (number) VALUES (10)" - name = "foo" - - assert_logged([[sql, name, []]]) do - @conn.insert_sql sql, name + with_example_table do + sql = "INSERT INTO ex (number) VALUES (10)" + name = "foo" + assert_logged [[sql, name, []]] do + @conn.insert_sql sql, name + end end end def test_insert_id_value_returned - sql = "INSERT INTO items (number) VALUES (10)" - idval = 'vuvuzela' - id = @conn.insert_sql sql, nil, nil, idval - assert_equal idval, id + with_example_table do + sql = "INSERT INTO ex (number) VALUES (10)" + idval = 'vuvuzela' + id = @conn.insert_sql sql, nil, nil, idval + assert_equal idval, id + end end def test_select_rows - 2.times do |i| - @conn.create "INSERT INTO items (number) VALUES (#{i})" + with_example_table do + 2.times do |i| + @conn.create "INSERT INTO ex (number) VALUES (#{i})" + end + rows = @conn.select_rows 'select number, id from ex' + assert_equal [[0, 1], [1, 2]], rows end - rows = @conn.select_rows 'select number, id from items' - assert_equal [[0, 1], [1, 2]], rows end def test_select_rows_logged - sql = "select * from items" - name = "foo" - - assert_logged([[sql, name, []]]) do - @conn.select_rows sql, name + with_example_table do + sql = "select * from ex" + name = "foo" + assert_logged [[sql, name, []]] do + @conn.select_rows sql, name + end end end def test_transaction - count_sql = 'select count(*) from items' + with_example_table do + count_sql = 'select count(*) from ex' - @conn.begin_db_transaction - @conn.create "INSERT INTO items (number) VALUES (10)" + @conn.begin_db_transaction + @conn.create "INSERT INTO ex (number) VALUES (10)" - assert_equal 1, @conn.select_rows(count_sql).first.first - @conn.rollback_db_transaction - assert_equal 0, @conn.select_rows(count_sql).first.first + assert_equal 1, @conn.select_rows(count_sql).first.first + @conn.rollback_db_transaction + assert_equal 0, @conn.select_rows(count_sql).first.first + end end def test_tables - assert_equal %w{ items }, @conn.tables - - @conn.execute <<-eosql - CREATE TABLE people ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer - ) - eosql - assert_equal %w{ items people }.sort, @conn.tables.sort + with_example_table do + assert_equal %w{ ex }, @conn.tables + with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do + assert_equal %w{ ex people }.sort, @conn.tables.sort + end + end end def test_tables_logs_name - assert_logged [['SCHEMA', []]] do + sql = <<-SQL + SELECT name FROM sqlite_master + WHERE type = 'table' AND NOT name = 'sqlite_sequence' + SQL + assert_logged [[sql.squish, 'SCHEMA', []]] do @conn.tables('hello') - assert_not_nil @subscriber.logged.first.shift end end def test_indexes_logs_name - assert_logged [["PRAGMA index_list(\"items\")", 'SCHEMA', []]] do - @conn.indexes('items', 'hello') + with_example_table do + assert_logged [["PRAGMA index_list(\"ex\")", 'SCHEMA', []]] do + @conn.indexes('ex', 'hello') + end end end def test_table_exists_logs_name - assert @conn.table_exists?('items') - assert_equal 'SCHEMA', @subscriber.logged[0][1] + with_example_table do + sql = <<-SQL + SELECT name FROM sqlite_master + WHERE type = 'table' + AND NOT name = 'sqlite_sequence' AND name = \"ex\" + SQL + assert_logged [[sql.squish, 'SCHEMA', []]] do + assert @conn.table_exists?('ex') + end + end end def test_columns - columns = @conn.columns('items').sort_by { |x| x.name } - assert_equal 2, columns.length - assert_equal %w{ id number }.sort, columns.map { |x| x.name } - assert_equal [nil, nil], columns.map { |x| x.default } - assert_equal [true, true], columns.map { |x| x.null } + with_example_table do + columns = @conn.columns('ex').sort_by { |x| x.name } + assert_equal 2, columns.length + assert_equal %w{ id number }.sort, columns.map { |x| x.name } + assert_equal [nil, nil], columns.map { |x| x.default } + assert_equal [true, true], columns.map { |x| x.null } + end end def test_columns_with_default - @conn.execute <<-eosql - CREATE TABLE columns_with_default ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer default 10 - ) - eosql - column = @conn.columns('columns_with_default').find { |x| - x.name == 'number' - } - assert_equal 10, column.default + with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer default 10' do + column = @conn.columns('ex').find { |x| + x.name == 'number' + } + assert_equal 10, column.default + end end def test_columns_with_not_null - @conn.execute <<-eosql - CREATE TABLE columns_with_default ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer not null - ) - eosql - column = @conn.columns('columns_with_default').find { |x| - x.name == 'number' - } - assert !column.null, "column should not be null" + with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer not null' do + column = @conn.columns('ex').find { |x| x.name == 'number' } + assert_not column.null, "column should not be null" + end end def test_indexes_logs - assert_difference('@subscriber.logged.length') do - @conn.indexes('items') + with_example_table do + assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do + @conn.indexes('ex') + end end - assert_match(/items/, @subscriber.logged.last.first) end def test_no_indexes @@ -348,41 +364,45 @@ module ActiveRecord end def test_index - @conn.add_index 'items', 'id', :unique => true, :name => 'fun' - index = @conn.indexes('items').find { |idx| idx.name == 'fun' } + with_example_table do + @conn.add_index 'ex', 'id', unique: true, name: 'fun' + index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } - assert_equal 'items', index.table - assert index.unique, 'index is unique' - assert_equal ['id'], index.columns + assert_equal 'ex', index.table + assert index.unique, 'index is unique' + assert_equal ['id'], index.columns + end end def test_non_unique_index - @conn.add_index 'items', 'id', :name => 'fun' - index = @conn.indexes('items').find { |idx| idx.name == 'fun' } - assert !index.unique, 'index is not unique' + with_example_table do + @conn.add_index 'ex', 'id', name: 'fun' + index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } + assert_not index.unique, 'index is not unique' + end end def test_compound_index - @conn.add_index 'items', %w{ id number }, :name => 'fun' - index = @conn.indexes('items').find { |idx| idx.name == 'fun' } - assert_equal %w{ id number }.sort, index.columns.sort + with_example_table do + @conn.add_index 'ex', %w{ id number }, name: 'fun' + index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } + assert_equal %w{ id number }.sort, index.columns.sort + end end def test_primary_key - assert_equal 'id', @conn.primary_key('items') - - @conn.execute <<-eosql - CREATE TABLE foos ( - internet integer PRIMARY KEY AUTOINCREMENT, - number integer not null - ) - eosql - assert_equal 'internet', @conn.primary_key('foos') + with_example_table do + assert_equal 'id', @conn.primary_key('ex') + with_example_table 'internet integer PRIMARY KEY AUTOINCREMENT, number integer not null', 'foos' do + assert_equal 'internet', @conn.primary_key('foos') + end + end end def test_no_primary_key - @conn.execute 'CREATE TABLE failboat (number integer not null)' - assert_nil @conn.primary_key('failboat') + with_example_table 'number integer not null' do + assert_nil @conn.primary_key('ex') + end end def test_supports_extensions @@ -400,10 +420,21 @@ module ActiveRecord private def assert_logged logs + subscriber = SQLSubscriber.new + subscription = ActiveSupport::Notifications.subscribe('sql.active_record', subscriber) yield - assert_equal logs, @subscriber.logged + assert_equal logs, subscriber.logged + ensure + ActiveSupport::Notifications.unsubscribe(subscription) end + def with_example_table(definition = nil, table_name = 'ex', &block) + definition ||= <<-SQL + id integer PRIMARY KEY AUTOINCREMENT, + number integer + SQL + super(@conn, table_name, definition, &block) + end end end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index a65f2da7a0..3b484a0d64 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -16,6 +16,8 @@ require 'models/essay' require 'models/toy' require 'models/invoice' require 'models/line_item' +require 'models/column' +require 'models/record' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, @@ -231,13 +233,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_belongs_to_counter debate = Topic.create("title" => "debate") - assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet" + assert_equal 0, debate.read_attribute("replies_count"), "No replies yet" trash = debate.replies.create("title" => "blah!", "content" => "world around!") - assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created" + assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created" trash.destroy - assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted" + assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted" end def test_belongs_to_counter_with_assigning_nil @@ -500,6 +502,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 4, topic.replies.size end + def test_concurrent_counter_cache_double_destroy + topic = Topic.create :title => "Zoom-zoom-zoom" + + 5.times do + topic.replies.create(:title => "re: zoom", :content => "speedy quick!") + end + + assert_equal 5, topic.reload[:replies_count] + assert_equal 5, topic.replies.size + + reply = topic.replies.first + reply_clone = Reply.find(reply.id) + + reply.destroy + assert_equal 4, topic.reload[:replies_count] + + reply_clone.destroy + assert_equal 4, topic.reload[:replies_count] + assert_equal 4, topic.replies.size + end + def test_custom_counter_cache reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") assert_equal 0, reply[:replies_count] @@ -885,4 +908,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end end end + + test 'belongs_to works with model called Record' do + record = Record.create! + Column.create! record: record + assert_equal 1, Column.count + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index e59161fc5b..7eaa5adc86 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -235,6 +235,17 @@ class EagerAssociationTest < ActiveRecord::TestCase end end + def test_finding_with_includes_on_empty_polymorphic_type_column + sponsor = sponsors(:moustache_club_sponsor_for_groucho) + sponsor.update!(sponsorable_type: '', sponsorable_id: nil) # sponsorable_type column might be declared NOT NULL + sponsor = assert_queries(1) do + assert_nothing_raised { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) } + end + assert_no_queries do + assert_equal nil, sponsor.sponsorable + end + end + def test_loading_from_an_association posts = authors(:david).posts.merge(:includes => :comments, :order => "posts.id").to_a assert_equal 2, posts.first.comments.size @@ -709,16 +720,16 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_invalid_association_reference - assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { Post.all.merge!(:includes=> :monkeys ).find(6) } - assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { Post.all.merge!(:includes=>[ :monkeys ]).find(6) } - assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { Post.all.merge!(:includes=>[ 'monkeys' ]).find(6) } - assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { + assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { Post.all.merge!(:includes=>[ :monkeys, :elephants ]).find(6) } end @@ -1187,6 +1198,14 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal authors(:bob), author end + test "preloading with a polymorphic association and using the existential predicate but also using a select" do + assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer + + assert_nothing_raised do + authors(:david).essays.includes(:writer).select(:name).any? + end + end + test "preloading with a polymorphic association and using the existential predicate" do assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 49d3301044..c79c0c87c5 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -22,6 +22,10 @@ require 'models/engine' require 'models/categorization' require 'models/minivan' require 'models/speedometer' +require 'models/reference' +require 'models/job' +require 'models/college' +require 'models/student' class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments @@ -39,7 +43,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :people, :posts, :readers, :taggings, :cars, :essays, - :categorizations + :categorizations, :jobs def setup Client.destroyed_client_ids.clear @@ -63,6 +67,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase dev.developer_projects.map(&:project_id).sort end + def test_has_many_build_with_options + college = College.create(name: 'UFMT') + Student.create(active: true, college_id: college.id, name: 'Sarah') + + assert_equal college.students, Student.where(active: true, college_id: college.id) + end + def test_create_from_association_should_respect_default_scope car = Car.create(:name => 'honda') assert_equal 'honda', car.name @@ -107,6 +118,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy" end + def test_delete_all_on_association_is_the_same_as_not_loaded + author = authors :david + author.thinking_posts.create!(:body => "test") + author.reload + expected_sql = capture_sql { author.thinking_posts.delete_all } + + author.thinking_posts.create!(:body => "test") + author.reload + author.thinking_posts.inspect + loaded_sql = capture_sql { author.thinking_posts.delete_all } + assert_equal(expected_sql, loaded_sql) + end + def test_building_the_associated_object_with_implicit_sti_base_class firm = DependentFirm.new company = firm.companies.build diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 026a7fe635..6675e19dd9 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1102,7 +1102,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [posts(:thinking)], person.reload.first_posts end - def test_has_many_through_with_includes_in_through_association_scope + test "has many through with includes in through association scope" do assert_not_empty posts(:welcome).author_address_extra_with_address end + + test "insert records via has_many_through association with scope" do + club = Club.create! + member = Member.create! + Membership.create!(club: club, member: member) + + club.favourites << member + assert_equal [member], club.favourites + + club.reload + assert_equal [member], club.favourites + end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 952baaca36..38e93288e4 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -288,10 +288,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_read_attribute topic = Topic.new topic.title = "Don't change the topic" - assert_equal "Don't change the topic", topic.send(:read_attribute, "title") + assert_equal "Don't change the topic", topic.read_attribute("title") assert_equal "Don't change the topic", topic["title"] - assert_equal "Don't change the topic", topic.send(:read_attribute, :title) + assert_equal "Don't change the topic", topic.read_attribute(:title) assert_equal "Don't change the topic", topic[:title] end @@ -358,10 +358,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase super(attr_name).upcase end - assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title") + assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute("title") assert_equal "STOP CHANGING THE TOPIC", topic["title"] - assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title) + assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute(:title) assert_equal "STOP CHANGING THE TOPIC", topic[:title] end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 6acb342d0b..2e5b8cffa6 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -533,6 +533,7 @@ class BasicsTest < ActiveRecord::TestCase def test_equality_of_new_records assert_not_equal Topic.new, Topic.new + assert_equal false, Topic.new == Topic.new end def test_equality_of_destroyed_records @@ -544,6 +545,47 @@ class BasicsTest < ActiveRecord::TestCase assert_equal topic_2, topic_1 end + def test_equality_with_blank_ids + one = Subscriber.new(:id => '') + two = Subscriber.new(:id => '') + assert_equal one, two + end + + def test_equality_of_relation_and_collection_proxy + car = Car.create! + car.bulbs.build + car.save + + assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation' + assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy' + end + + def test_equality_of_relation_and_array + car = Car.create! + car.bulbs.build + car.save + + assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array' + end + + def test_equality_of_relation_and_association_relation + car = Car.create! + car.bulbs.build + car.save + + assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation' + assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation' + end + + def test_equality_of_collection_proxy_and_association_relation + car = Car.create! + car.bulbs.build + car.save + + assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation' + assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy' + end + def test_hashing assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end @@ -578,12 +620,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal nil, Topic.find_by_id(topic.id) end - def test_blank_ids - one = Subscriber.new(:id => '') - two = Subscriber.new(:id => '') - assert_equal one, two - end - def test_comparison_with_different_objects topic = Topic.create category = Category.create(:name => "comparison") diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 24eb91bef9..40f73cd68c 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -20,13 +20,13 @@ module ActiveRecord def setup super @connection = ActiveRecord::Base.connection - @listener = LogListener.new + @subscriber = LogListener.new @pk = Topic.columns.find { |c| c.primary } - ActiveSupport::Notifications.subscribe('sql.active_record', @listener) + @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) end teardown do - ActiveSupport::Notifications.unsubscribe(@listener) + ActiveSupport::Notifications.unsubscribe(@subscription) end if ActiveRecord::Base.connection.supports_statement_cache? @@ -37,7 +37,7 @@ module ActiveRecord @connection.exec_query(sql, 'SQL', binds) - message = @listener.calls.find { |args| args[4][:sql] == sql } + message = @subscriber.calls.find { |args| args[4][:sql] == sql } assert_equal binds, message[4][:binds] end @@ -48,14 +48,14 @@ module ActiveRecord @connection.exec_query(sql, 'SQL', binds) - message = @listener.calls.find { |args| args[4][:sql] == sql } + message = @subscriber.calls.find { |args| args[4][:sql] == sql } assert_equal [[@pk, 3]], message[4][:binds] end def test_find_one_uses_binds Topic.find(1) binds = [[@pk, 1]] - message = @listener.calls.find { |args| args[4][:binds] == binds } + message = @subscriber.calls.find { |args| args[4][:binds] == binds } assert message, 'expected a message with binds' end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index db999f90ab..b8de78934e 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -387,6 +387,20 @@ class CalculationsTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Account.count(1, 2, 3) } end + def test_count_with_order + assert_equal 6, Account.order(:credit_limit).count + end + + def test_count_with_reverse_order + assert_equal 6, Account.order(:credit_limit).reverse_order.count + end + + def test_count_with_where_and_order + assert_equal 1, Account.where(firm_name: '37signals').count + assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).count + assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count + end + def test_should_sum_expression # Oracle adapter returns floating point value 636.0 after SUM if current_adapter?(:OracleAdapter) diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index c7b64f29c3..c1dd1f1c69 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -127,13 +127,13 @@ module ActiveRecord if current_adapter?(:PostgreSQLAdapter) def test_bigint_column_should_map_to_integer - oid = PostgreSQLAdapter::OID::Identity.new + oid = PostgreSQLAdapter::OID::Integer.new bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint") assert_equal :integer, bigint_column.type end def test_smallint_column_should_map_to_integer - oid = PostgreSQLAdapter::OID::Identity.new + oid = PostgreSQLAdapter::OID::Integer.new smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint") assert_equal :integer, smallint_column.type end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 2992ceb211..f2d18e812d 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -17,6 +17,64 @@ module ActiveRecord ENV["DATABASE_URL"] = @previous_database_url end + def resolve(spec, config) + ConnectionSpecification::Resolver.new(klass.new(config).resolve).resolve(spec) + end + + def spec(spec, config) + ConnectionSpecification::Resolver.new(klass.new(config).resolve).spec(spec) + end + + def test_resolver_with_database_uri_and_current_env_symbol_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = resolve(:default_env, config) + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_and_current_env_string_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = assert_deprecated { resolve("default_env", config) } + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_known_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } + actual = resolve(:production, config) + expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_unknown_symbol_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + assert_raises AdapterNotSpecified do + resolve(:production, config) + end + end + + def test_resolver_with_database_uri_and_unknown_string_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + assert_deprecated do + assert_raises AdapterNotSpecified do + spec("production", config) + end + end + end + + def test_resolver_with_database_uri_and_supplied_url + ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo" + config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } } + actual = resolve("postgres://localhost/foo", config) + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + def test_jdbc_url config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } } actual = klass.new(config).resolve @@ -25,16 +83,24 @@ module ActiveRecord def test_environment_does_not_exist_in_config_url_does_exist ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = klass.new(config).resolve expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } - assert_equal expect_prod, actual["production"] + assert_equal expect_prod, actual["default_env"] + end + + def test_url_with_hyphenated_scheme + ENV['DATABASE_URL'] = "ibm-db://localhost/foo" + config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } + actual = resolve(:default_env, config) + expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual end def test_string_connection - config = { "production" => "postgres://localhost/foo" } + config = { "default_env" => "postgres://localhost/foo" } actual = klass.new(config).resolve - expected = { "production" => + expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" @@ -44,9 +110,9 @@ module ActiveRecord end def test_url_sub_key - config = { "production" => { "url" => "postgres://localhost/foo" } } + config = { "default_env" => { "url" => "postgres://localhost/foo" } } actual = klass.new(config).resolve - expected = { "production" => + expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" @@ -75,9 +141,10 @@ module ActiveRecord expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } - assert_equal expected, actual["production"] - assert_equal expected, actual["development"] - assert_equal expected, actual["test"] + assert_equal expected, actual["default_env"] + assert_equal nil, actual["production"] + assert_equal nil, actual["development"] + assert_equal nil, actual["test"] assert_equal nil, actual[:production] assert_equal nil, actual[:development] assert_equal nil, actual[:test] @@ -86,9 +153,9 @@ module ActiveRecord def test_url_sub_key_with_database_url ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO" - config = { "production" => { "url" => "postgres://localhost/foo" } } + config = { "default_env" => { "url" => "postgres://localhost/foo" } } actual = klass.new(config).resolve - expected = { "production" => + expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" @@ -100,9 +167,9 @@ module ActiveRecord def test_merge_no_conflicts_with_database_url ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = {"production" => { "pool" => "5" } } + config = {"default_env" => { "pool" => "5" } } actual = klass.new(config).resolve - expected = { "production" => + expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", @@ -115,9 +182,9 @@ module ActiveRecord def test_merge_conflicts_with_database_url ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = {"production" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } + config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } actual = klass.new(config).resolve - expected = { "production" => + expected = { "default_env" => { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index fdd1914cba..3c2f5d4219 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -82,15 +82,34 @@ module ActiveRecord assert_equal password, spec["password"] end - def test_url_host_db_for_sqlite3 - spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test' + def test_url_with_authority_for_sqlite3 + spec = resolve 'sqlite3:///foo_test' assert_equal('/foo_test', spec["database"]) end - def test_url_host_memory_db_for_sqlite3 - spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:' + def test_url_absolute_path_for_sqlite3 + spec = resolve 'sqlite3:/foo_test' + assert_equal('/foo_test', spec["database"]) + end + + def test_url_relative_path_for_sqlite3 + spec = resolve 'sqlite3:foo_test' + assert_equal('foo_test', spec["database"]) + end + + def test_url_memory_db_for_sqlite3 + spec = resolve 'sqlite3::memory:' assert_equal(':memory:', spec["database"]) end + + def test_url_sub_key_for_sqlite3 + spec = resolve :production, 'production' => {"url" => 'sqlite3:foo?encoding=utf8'} + assert_equal({ + "adapter" => "sqlite3", + "database" => "foo", + "encoding" => "utf8" }, spec) + end + end end end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index f8ebd7caee..3b2f0dfe07 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -194,6 +194,7 @@ class EnumTest < ActiveRecord::TestCase :valid, # generates #valid?, which conflicts with an AR method :save, # generates #save!, which conflicts with an AR method :proposed, # same value as an existing enum + :public, :private, :protected, # generates a method that conflict with ruby words ] conflicts.each_with_index do |value, i| @@ -249,4 +250,40 @@ class EnumTest < ActiveRecord::TestCase valid_book = klass.new(status: "written") assert valid_book.valid? end + + test "enums are distinct per class" do + klass1 = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:proposed, :written] + end + + klass2 = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:drafted, :uploaded] + end + + book1 = klass1.proposed.create! + book1.status = :written + assert_equal ['proposed', 'written'], book1.status_change + + book2 = klass2.drafted.create! + book2.status = :uploaded + assert_equal ['drafted', 'uploaded'], book2.status_change + end + + test "enums are inheritable" do + subklass1 = Class.new(Book) + + subklass2 = Class.new(Book) do + enum status: [:drafted, :uploaded] + end + + book1 = subklass1.proposed.create! + book1.status = :written + assert_equal ['proposed', 'written'], book1.status_change + + book2 = subklass2.drafted.create! + book2.status = :uploaded + assert_equal ['drafted', 'uploaded'], book2.status_change + end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 1147418815..8bbc0af758 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -254,7 +254,7 @@ class FixturesTest < ActiveRecord::TestCase def test_fixtures_are_set_up_with_database_env_variable db_url_tmp = ENV['DATABASE_URL'] - ENV['DATABASE_URL'] = "sqlite3:///:memory:" + ENV['DATABASE_URL'] = "sqlite3::memory:" ActiveRecord::Base.stubs(:configurations).returns({}) test_case = Class.new(ActiveRecord::TestCase) do fixtures :accounts @@ -677,6 +677,12 @@ end class FoxyFixturesTest < ActiveRecord::TestCase fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" + if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' + require 'models/uuid_parent' + require 'models/uuid_child' + fixtures :uuid_parents, :uuid_children + end + def test_identifies_strings assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo")) assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO")) @@ -689,6 +695,9 @@ class FoxyFixturesTest < ActiveRecord::TestCase def test_identifies_consistently assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby) assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2) + + assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid) + assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid) end TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 8a49dfbb44..eaf2cada9d 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -111,6 +111,15 @@ def verify_default_timezone_config end end +def enable_uuid_ossp!(connection) + return false unless connection.supports_extensions? + return true if connection.extension_enabled?('uuid-ossp') + + connection.enable_extension 'uuid-ossp' + connection.commit_db_transaction + connection.reconnect! +end + unless ENV['FIXTURE_DEBUG'] module ActiveRecord::TestFixtures::ClassMethods def try_to_load_dependency_with_silence(*args) @@ -168,7 +177,7 @@ class SQLSubscriber def start(name, id, payload) @payloads << payload - @logged << [payload[:sql], payload[:name], payload[:binds]] + @logged << [payload[:sql].squish, payload[:name], payload[:binds]] end def finish(name, id, payload); end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index ccf19fb4d0..6a02873cba 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -35,6 +35,14 @@ module ActiveRecord assert_no_column TestModel, :last_name end + def test_add_column_without_limit + # TODO: limit: nil should work with all adapters. + skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + add_column :test_models, :description, :string, limit: nil + TestModel.reset_column_information + assert_nil TestModel.columns_hash["description"].limit + end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_unabstracted_database_dependent_types add_column :test_models, :intelligence_quotient, :tinyint diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index d7ad5ed29f..c085fcf161 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase def test_column_string_type_and_limit assert_equal :string, @first.column_for_attribute("title").type - assert_equal 255, @first.column_for_attribute("title").limit + assert_equal 250, @first.column_for_attribute("title").limit end def test_column_null_not_null @@ -87,6 +87,14 @@ class ReflectionTest < ActiveRecord::TestCase end end + def test_irregular_reflection_class_name + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'plural_irregular', 'plurales_irregulares' + end + reflection = AssociationReflection.new(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base) + assert_equal 'PluralIrregular', reflection.class_name + end + def test_aggregation_reflection reflection_for_address = AggregateReflection.new( :composed_of, :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer @@ -192,7 +200,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_reflection_should_not_raise_error_when_compared_to_other_object - assert_nothing_raised { Firm.reflections[:clients] == Object.new } + assert_nothing_raised { Firm.reflections['clients'] == Object.new } end def test_has_many_through_reflection diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 4fafa668fb..c81a3002d6 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -107,10 +107,18 @@ module ActiveRecord end test 'reverse_order!' do - assert relation.reverse_order!.equal?(relation) - assert relation.reverse_order_value + relation = Post.order('title ASC, comments_count DESC') + + relation.reverse_order! + + assert_equal 'title DESC', relation.order_values.first + assert_equal 'comments_count ASC', relation.order_values.last + + relation.reverse_order! - assert !relation.reverse_order_value + + assert_equal 'title ASC', relation.order_values.first + assert_equal 'comments_count DESC', relation.order_values.last end test 'create_with!' do diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 15611656fd..fb0b906c07 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -16,6 +16,10 @@ module ActiveRecord def self.connection Post.connection end + + def self.table_name + 'fake_table' + end end def test_construction diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fddb7c204a..d054dfa25a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -14,6 +14,7 @@ require 'models/car' require 'models/engine' require 'models/tyre' require 'models/minivan' +require 'models/aircraft' class RelationTest < ActiveRecord::TestCase @@ -365,6 +366,16 @@ class RelationTest < ActiveRecord::TestCase assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) end + + def test_null_relation_count + ac = Aircraft.new + assert_equal Hash.new, ac.engines.group(:id).count + assert_equal 0, ac.engines.count + ac.save + assert_equal Hash.new, ac.engines.group(:id).count + assert_equal 0, ac.engines.count + end + def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end @@ -573,6 +584,12 @@ class RelationTest < ActiveRecord::TestCase assert_equal expected, actual end + def test_to_sql_on_scoped_proxy + auth = Author.first + Post.where("1=1").written_by(auth) + assert_not auth.posts.to_sql.include?("1=1") + end + def test_loading_with_one_association_with_non_preload posts = Post.eager_load(:last_comment).order('comments.id DESC') post = posts.find { |p| p.id == 1 } @@ -818,6 +835,16 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } end + def test_select_with_aggregates + posts = Post.select(:title, :body) + + assert_equal 11, posts.count(:all) + assert_equal 11, posts.size + assert posts.any? + assert posts.many? + assert_not posts.empty? + end + def test_select_takes_a_variable_list_of_args david = developers(:david) @@ -848,6 +875,17 @@ class RelationTest < ActiveRecord::TestCase assert_equal 9, posts.where(:comments_count => 0).count end + def test_count_on_association_relation + author = Author.last + another_author = Author.first + posts = Post.where(author_id: author.id) + + assert_equal author.posts.where(author_id: author.id).size, posts.count + + assert_equal 0, author.posts.where(author_id: another_author.id).size + assert author.posts.where(author_id: another_author.id).empty? + end + def test_count_with_distinct posts = Post.all @@ -1418,6 +1456,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal [], scope.references_values end + def test_order_with_reorder_nil_removes_the_order + relation = Post.order(:title).reorder(nil) + + assert_nil relation.order_values.first + end + + def test_reverse_order_with_reorder_nil_removes_the_order + relation = Post.order(:title).reverse_order.reorder(nil) + + assert_nil relation.order_values.first + end + def test_presence topics = Topic.all diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 954eab8022..c7cc214c3f 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -51,4 +51,31 @@ class SanitizeTest < ActiveRecord::TestCase select_author_sql = Post.send(:sanitize_sql_array, ['']) assert_equal('', select_author_sql) end + + def test_sanitize_sql_like + assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%') + assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string') + assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint') + assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42') + end + + def test_sanitize_sql_like_with_custom_escape_character + assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!') + assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!') + assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!') + assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!') + assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!') + end + + def test_sanitize_sql_like_example_use_case + searchable_post = Class.new(Post) do + def self.search(term) + where("title LIKE ?", sanitize_sql_like(term, '!')) + end + end + + assert_sql /LIKE '20!% !_reduction!_!!'/ do + searchable_post.search("20% _reduction_!").to_a + end + end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 575eb34a9c..fd0ef2f89f 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -63,7 +63,7 @@ class SchemaDumperTest < ActiveRecord::TestCase next if column_set.empty? lengths = column_set.map do |column| - if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/) + if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid)\s+"/) match[0].length end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index f0ad9ebb8a..59ec2dd6a4 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -291,6 +291,9 @@ class NamedScopingTest < ActiveRecord::TestCase :relation, # private class method on AR::Base :new, # redefined class method on AR::Base :all, # a default scope + :public, + :protected, + :private ] non_conflicts = [ diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 4476ce3410..803a054d7e 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -19,10 +19,14 @@ module ActiveRecord end end - def assert_sql(*patterns_to_match) + def capture_sql SQLCounter.clear_log yield - SQLCounter.log_all + SQLCounter.log_all.dup + end + + def assert_sql(*patterns_to_match) + capture_sql { yield } ensure failed_patterns = [] patterns_to_match.each do |pattern| diff --git a/activerecord/test/fixtures/uuid_children.yml b/activerecord/test/fixtures/uuid_children.yml new file mode 100644 index 0000000000..a7b15016e2 --- /dev/null +++ b/activerecord/test/fixtures/uuid_children.yml @@ -0,0 +1,3 @@ +sonny: + uuid_parent: daddy + name: Sonny diff --git a/activerecord/test/fixtures/uuid_parents.yml b/activerecord/test/fixtures/uuid_parents.yml new file mode 100644 index 0000000000..0b40225c5c --- /dev/null +++ b/activerecord/test/fixtures/uuid_parents.yml @@ -0,0 +1,2 @@ +daddy: + name: Daddy diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 566e0873f1..a762ad4bb5 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -6,6 +6,8 @@ class Club < ActiveRecord::Base has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" belongs_to :category + has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member + private def private_method diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb index c7495d7deb..501af4a8dd 100644 --- a/activerecord/test/models/college.rb +++ b/activerecord/test/models/college.rb @@ -1,5 +1,10 @@ require_dependency 'models/arunit2_model' +require 'active_support/core_ext/object/with_options' class College < ARUnit2Model has_many :courses + + with_options dependent: :destroy do |assoc| + assoc.has_many :students, -> { where(active: true) } + end end diff --git a/activerecord/test/models/column.rb b/activerecord/test/models/column.rb new file mode 100644 index 0000000000..499358b4cf --- /dev/null +++ b/activerecord/test/models/column.rb @@ -0,0 +1,3 @@ +class Column < ActiveRecord::Base + belongs_to :record +end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 7bb0caf44b..e472cf951d 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -18,7 +18,6 @@ class Pirate < ActiveRecord::Base has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates - # These both have :autosave enabled because accepts_nested_attributes_for is used on them. has_one :ship has_one :update_only_ship, :class_name => 'Ship' has_one :non_validated_ship, :class_name => 'Ship' diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index faf539a562..099e039255 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -149,6 +149,10 @@ class Post < ActiveRecord::Base ranked_by_comments.limit_by(limit) end + def self.written_by(author) + where(id: author.posts.pluck(:id)) + end + def self.reset_log @log = [] end diff --git a/activerecord/test/models/record.rb b/activerecord/test/models/record.rb new file mode 100644 index 0000000000..f77ac9fc03 --- /dev/null +++ b/activerecord/test/models/record.rb @@ -0,0 +1,2 @@ +class Record < ActiveRecord::Base +end diff --git a/activerecord/test/models/student.rb b/activerecord/test/models/student.rb index f459f2a9a3..28a0b6c99b 100644 --- a/activerecord/test/models/student.rb +++ b/activerecord/test/models/student.rb @@ -1,3 +1,4 @@ class Student < ActiveRecord::Base has_and_belongs_to_many :lessons + belongs_to :college end diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb index a581b381e8..80d4725f7e 100644 --- a/activerecord/test/models/tag.rb +++ b/activerecord/test/models/tag.rb @@ -3,5 +3,5 @@ class Tag < ActiveRecord::Base has_many :taggables, :through => :taggings has_one :tagging - has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post' -end
\ No newline at end of file + has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post' +end diff --git a/activerecord/test/models/uuid_child.rb b/activerecord/test/models/uuid_child.rb new file mode 100644 index 0000000000..a3d0962ad6 --- /dev/null +++ b/activerecord/test/models/uuid_child.rb @@ -0,0 +1,3 @@ +class UuidChild < ActiveRecord::Base + belongs_to :uuid_parent +end diff --git a/activerecord/test/models/uuid_parent.rb b/activerecord/test/models/uuid_parent.rb new file mode 100644 index 0000000000..5634f22d0c --- /dev/null +++ b/activerecord/test/models/uuid_parent.rb @@ -0,0 +1,3 @@ +class UuidParent < ActiveRecord::Base + has_many :uuid_children +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index b44e72a67c..da3074e90f 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -17,6 +17,15 @@ ActiveRecord::Schema.define do ActiveRecord::Base.connection.create_table(*args, &block) ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000" end + when "PostgreSQL" + enable_uuid_ossp!(ActiveRecord::Base.connection) + create_table :uuid_parents, id: :uuid, force: true do |t| + t.string :name + end + create_table :uuid_children, id: :uuid, force: true do |t| + t.string :name + t.uuid :uuid_parent_id + end end @@ -161,6 +170,10 @@ ActiveRecord::Schema.define do t.integer :references, null: false end + create_table :columns, force: true do |t| + t.references :record + end + create_table :comments, force: true do |t| t.integer :post_id, null: false # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in @@ -638,6 +651,8 @@ ActiveRecord::Schema.define do create_table :students, force: true do |t| t.string :name + t.boolean :active + t.integer :college_id end create_table :subscribers, force: true, id: false do |t| @@ -671,7 +686,7 @@ ActiveRecord::Schema.define do end create_table :topics, force: true do |t| - t.string :title + t.string :title, limit: 250 t.string :author_name t.string :author_email_address if mysql_56? @@ -817,6 +832,8 @@ ActiveRecord::Schema.define do t.integer :department_id end + create_table :records, force: true do |t| + end except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk diff --git a/activerecord/test/support/connection_helper.rb b/activerecord/test/support/connection_helper.rb new file mode 100644 index 0000000000..4a19e5df44 --- /dev/null +++ b/activerecord/test/support/connection_helper.rb @@ -0,0 +1,14 @@ +module ConnectionHelper + def run_without_connection + original_connection = ActiveRecord::Base.remove_connection + yield original_connection + ensure + ActiveRecord::Base.establish_connection(original_connection) + end + + # Used to drop all cache query plans in tests. + def reset_connection + original_connection = ActiveRecord::Base.remove_connection + ActiveRecord::Base.establish_connection(original_connection) + end +end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 556e94d184..5e430d20fa 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,48 @@ +* Add `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5` to support stable + UUID fixtures on PostgreSQL. + + *Roderick van Domburg* + +* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is + true. + + This fixes the current situation of: + + 1.second.eql?(1.second) #=> false + + `eql?` also requires that the other object is an `ActiveSupport::Duration`. + This requirement makes `ActiveSupport::Duration`'s behavior consistent with + the behavior of Ruby's numeric types: + + 1.eql?(1.0) #=> false + 1.0.eql?(1) #=> false + + 1.second.eql?(1) #=> false (was true) + 1.eql?(1.second) #=> false + + { 1 => "foo", 1.0 => "bar" } + #=> { 1 => "foo", 1.0 => "bar" } + + { 1 => "foo", 1.second => "bar" } + # now => { 1 => "foo", 1.second => "bar" } + # was => { 1 => "bar" } + + And though the behavior of these hasn't changed, for reference: + + 1 == 1.0 #=> true + 1.0 == 1 #=> true + + 1 == 1.second #=> true + 1.second == 1 #=> true + + *Emily Dobervich* + +* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies + instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is + deprecated. + + *Pavel Pravosud* + * `HashWithIndifferentAccess` better respects `#to_hash` on objects it's given. In particular, `.new`, `#update`, `#merge`, `#replace` all accept objects which respond to `#to_hash`, even if those objects are not Hashes diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index e9ee98a128..73c6b3cb88 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/string/inflections' +require 'active_support/per_thread_registry' module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e14ece7f35..05ca943776 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -131,8 +131,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) lambda { |env| target = env.target @@ -149,6 +147,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback, halted_lambda, filter) lambda { |env| @@ -166,6 +165,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -178,6 +178,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -185,6 +186,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :simple end class After @@ -208,8 +210,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions) lambda { |env| env = next_callback.call env @@ -223,6 +223,7 @@ module ActiveSupport env } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback) lambda { |env| @@ -233,6 +234,7 @@ module ActiveSupport env } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -246,6 +248,7 @@ module ActiveSupport env } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -254,6 +257,7 @@ module ActiveSupport env } end + private_class_method :simple end class Around @@ -269,8 +273,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions) lambda { |env| target = env.target @@ -288,6 +290,7 @@ module ActiveSupport end } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback) lambda { |env| @@ -305,6 +308,7 @@ module ActiveSupport end } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -322,6 +326,7 @@ module ActiveSupport end } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -332,6 +337,7 @@ module ActiveSupport env } end + private_class_method :simple end end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index aa19aed43b..293a3b2619 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/kernel/agnostics' require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/debugger' +require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9cd7485e2e..3d2c809c9f 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -78,6 +78,9 @@ end require 'bigdecimal' class BigDecimal + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. begin BigDecimal.new('4.56').dup diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..fec8f7c0ec --- /dev/null +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,47 @@ +module SecureRandom + UUID_DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs. + # ::uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack('NnnnnN') + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for ::uuid_from_hash using Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + self.uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for ::uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + self.uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + class << self + # Alias for ::uuid. + alias_method :uuid_v4, :uuid + end +end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index cf9b1a4ec0..18273573e0 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -130,6 +130,8 @@ class String # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' # # See also +deconstantize+. def demodulize diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index eb02b6a442..2c8995be9a 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,5 +1,6 @@ require 'erb' require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/deprecation' class ERB module Util @@ -124,7 +125,7 @@ module ActiveSupport #:nodoc: class SafeBuffer < String UNSAFE_STRING_METHODS = %w( capitalize chomp chop delete downcase gsub lstrip next reverse rstrip - slice squeeze strip sub succ swapcase tr tr_s upcase prepend + slice squeeze strip sub succ swapcase tr tr_s upcase ) alias_method :original_concat, :concat @@ -169,15 +170,18 @@ module ActiveSupport #:nodoc: self[0, 0] end - def concat(value) - if !html_safe? || value.html_safe? - super(value) - else - super(ERB::Util.h(value)) + %w[concat prepend].each do |method_name| + define_method method_name do |value| + super(html_escape_interpolated_argument(value)) end end alias << concat + def prepend!(value) + ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend + prepend value + end + def +(other) dup.concat(other) end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 7df4857c25..09eb732ef7 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -49,6 +49,10 @@ module ActiveSupport end end + def eql?(other) + other.is_a?(Duration) && self == other + end + def self.===(other) #:nodoc: other.is_a?(Duration) rescue ::NoMethodError diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a270c4452f..6229d15619 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -172,6 +172,8 @@ module ActiveSupport # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' # # See also +deconstantize+. def demodulize(path) diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index e55ffd12c3..dea84e437f 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -12,7 +12,7 @@ module ActiveSupport private def method_missing(method, *arguments, &block) - if arguments.last.is_a?(Proc) + if arguments.first.is_a?(Proc) proc = arguments.pop arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } else diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index 86193c2b07..447b1d10ad 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -33,7 +33,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_simple_accessor_declaration - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :both end @@ -48,7 +48,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_simple_accessor_declaration_with_instance_reader_false _instance_methods = single_class.public_instance_methods - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false end @@ -60,7 +60,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_working_with_simple_attributes - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :both end @@ -79,7 +79,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_child_class_delegates_to_parent_but_can_be_overridden parent = Class.new - ActiveSupport::Deprecation.silence do + assert_deprecated do parent.superclass_delegating_accessor :both end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 28ba33331e..c8f17f4618 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -31,6 +31,13 @@ class DurationTest < ActiveSupport::TestCase assert !(1.day == 'foo') end + def test_eql + assert 1.minute.eql?(1.minute) + assert 2.days.eql?(48.hours) + assert !1.second.eql?(1) + assert !1.eql?(1.second) + end + def test_inspect assert_equal '0 seconds', 0.seconds.inspect assert_equal '1 month', 1.month.inspect diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 18b251173f..d8bf81d02b 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -137,4 +137,4 @@ class KernelDebuggerTest < ActiveSupport::TestCase ensure Object.send(:remove_const, :Rails) end -end +end if RUBY_VERSION < '2.0.0' diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index 91d558dbb5..91d558dbb5 100644 --- a/activesupport/test/core_ext/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index e0566e012c..84512380cf 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -5,34 +5,27 @@ require 'active_support/core_ext/numeric/time' class DuplicableTest < ActiveSupport::TestCase RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds] - YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - NO = [] + ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. begin bd = BigDecimal.new('4.56') - YES << bd.dup + ALLOW_DUP << bd.dup rescue TypeError RAISE_DUP << bd end - def test_duplicable - (RAISE_DUP + NO).each do |v| + RAISE_DUP.each do |v| assert !v.duplicable? + assert_raises(TypeError, v.class.name) { v.dup } end - YES.each do |v| - assert v.duplicable?, "#{v.class} should be duplicable" - end - - (YES + NO).each do |v| + ALLOW_DUP.each do |v| + assert v.duplicable?, "#{ v.class } should be duplicable" assert_nothing_raised { v.dup } end - - RAISE_DUP.each do |v| - assert_raises(TypeError, v.class.name) do - v.dup - end - end end end diff --git a/activesupport/test/core_ext/securerandom_test.rb b/activesupport/test/core_ext/securerandom_test.rb new file mode 100644 index 0000000000..71980f6910 --- /dev/null +++ b/activesupport/test/core_ext/securerandom_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'active_support/core_ext/securerandom' + +class SecureRandomExt < ActiveSupport::TestCase + def test_v3_uuids + assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", SecureRandom.uuid_v3(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com") + assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", SecureRandom.uuid_v3(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com") + assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", SecureRandom.uuid_v3(SecureRandom::UUID_OID_NAMESPACE, "1.2.3") + assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", SecureRandom.uuid_v3(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US") + end + + def test_v5_uuids + assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", SecureRandom.uuid_v5(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com") + assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", SecureRandom.uuid_v5(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com") + assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, "1.2.3") + assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", SecureRandom.uuid_v5(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US") + end + + def test_uuid_v4_alias + assert_equal SecureRandom.method(:uuid_v4), SecureRandom.method(:uuid) + end + + def test_invalid_hash_class + assert_raise ArgumentError do + SecureRandom.uuid_from_hash(Digest::SHA2, SecureRandom::UUID_OID_NAMESPACE, '1.2.3') + end + end +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 072b970a2d..ea12f1ced5 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -608,6 +608,29 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !@other_combination.html_safe? end + test "Prepending safe onto unsafe yields unsafe" do + @string.prepend "other".html_safe + assert !@string.html_safe? + assert_equal @string, "otherhello" + end + + test "Prepending unsafe onto safe yields escaped safe" do + other = "other".html_safe + other.prepend "<foo>" + assert other.html_safe? + assert_equal other, "<foo>other" + end + + test "Deprecated #prepend! method is still present" do + other = "other".html_safe + + assert_deprecated do + other.prepend! "<foo>" + end + + assert_equal other, "<foo>other" + end + test "Concatting safe onto unsafe yields unsafe" do @other_string = "other" diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 35967ba656..b0b4738eb3 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -200,6 +200,7 @@ class InflectorTest < ActiveSupport::TestCase def test_demodulize assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account") assert_equal "Account", ActiveSupport::Inflector.demodulize("Account") + assert_equal "Account", ActiveSupport::Inflector.demodulize("::Account") assert_equal "", ActiveSupport::Inflector.demodulize("") end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 753effb54e..f49431cbbf 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -1,6 +1,9 @@ require 'abstract_unit' require 'active_support/xml_mini' require 'active_support/builder' +require 'active_support/core_ext/array' +require 'active_support/core_ext/hash' +require 'active_support/core_ext/big_decimal' module XmlMiniTest class RenameKeyTest < ActiveSupport::TestCase @@ -88,6 +91,61 @@ module XmlMiniTest assert_xml "<b>Howdy</b>" end + test "#to_tag should use the type value in the options hash" do + @xml.to_tag(:b, "blue", @options.merge(type: 'color')) + assert_xml( "<b type=\"color\">blue</b>" ) + end + + test "#to_tag accepts symbol types" do + @xml.to_tag(:b, :name, @options) + assert_xml( "<b type=\"symbol\">name</b>" ) + end + + test "#to_tag accepts boolean types" do + @xml.to_tag(:b, true, @options) + assert_xml( "<b type=\"boolean\">true</b>") + end + + test "#to_tag accepts float types" do + @xml.to_tag(:b, 3.14, @options) + assert_xml( "<b type=\"float\">3.14</b>") + end + + test "#to_tag accepts decimal types" do + @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options) + assert_xml( "<b type=\"decimal\">1.2</b>") + end + + test "#to_tag accepts date types" do + @xml.to_tag(:b, Date.new(2001,2,3), @options) + assert_xml( "<b type=\"date\">2001-02-03</b>") + end + + test "#to_tag accepts datetime types" do + @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options) + assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>") + end + + test "#to_tag accepts time types" do + @xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options) + assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>") + end + + test "#to_tag accepts array types" do + @xml.to_tag(:b, ["first_name", "last_name"], @options) + assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" ) + end + + test "#to_tag accepts hash types" do + @xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options) + assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" ) + end + + test "#to_tag should not add type when skip types option is set" do + @xml.to_tag(:b, "Bob", @options.merge(skip_types: 1)) + assert_xml( "<b>Bob</b>" ) + end + test "#to_tag should dasherize the space when passed a string with spaces as a key" do @xml.to_tag("New York", 33, @options) assert_xml "<New---York type=\"integer\">33</New---York>" @@ -97,7 +155,6 @@ module XmlMiniTest @xml.to_tag(:"New York", 33, @options) assert_xml "<New---York type=\"integer\">33</New---York>" end - # TODO: test the remaining functions hidden in #to_tag. end class WithBackendTest < ActiveSupport::TestCase diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index a7c215c295..14ad4fd424 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1 +1,9 @@ +* Updates the maintenance policy to match the latest versions of Rails + + *Matias Korhonen* + +* Switched the order of `Applying a default scope` and `Merging of scopes` subsections so default scopes are introduced first. + + *Alex Riabov* + Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/Rakefile b/guides/Rakefile index d6dd950d01..94d4be8c0a 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -13,8 +13,8 @@ namespace :guides do desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing" task :kindle do - unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ - abort "Please `gem install kindlerb`" + unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ + abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH" end unless `convert` =~ /convert/ abort "Please install ImageMagick`" diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile index 05de8bb536..ba6b733dd2 100644 --- a/guides/code/getting_started/Rakefile +++ b/guides/code/getting_started/Rakefile @@ -3,4 +3,4 @@ require File.expand_path('../config/application', __FILE__) -Blog::Application.load_tasks +Rails.application.load_tasks diff --git a/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru index ddf869e921..5bc2a619e8 100644 --- a/guides/code/getting_started/config.ru +++ b/guides/code/getting_started/config.ru @@ -1,4 +1,4 @@ # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) -run Blog::Application +run Rails.application diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb index e7e341c960..ee8d90dc65 100644 --- a/guides/code/getting_started/config/environment.rb +++ b/guides/code/getting_started/config/environment.rb @@ -2,4 +2,4 @@ require File.expand_path('../application', __FILE__) # Initialize the Rails application. -Blog::Application.initialize! +Rails.application.initialize! diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index 7e5692b08b..ae9ffe209a 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -1,4 +1,4 @@ -Blog::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb index 8c514e065e..c8ae858574 100644 --- a/guides/code/getting_started/config/environments/production.rb +++ b/guides/code/getting_started/config/environments/production.rb @@ -1,4 +1,4 @@ -Blog::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb index 34ab1530d1..680d0b9e06 100644 --- a/guides/code/getting_started/config/environments/test.rb +++ b/guides/code/getting_started/config/environments/test.rb @@ -1,4 +1,4 @@ -Blog::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb index aaf57731be..c2a549c299 100644 --- a/guides/code/getting_started/config/initializers/secret_token.rb +++ b/guides/code/getting_started/config/initializers/secret_token.rb @@ -9,4 +9,4 @@ # Make sure your secret_key_base is kept private # if you're sharing your code publicly. -Blog::Application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53' +Rails.application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53' diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb index 3b2ca93ab9..1b9fa324d4 100644 --- a/guides/code/getting_started/config/initializers/session_store.rb +++ b/guides/code/getting_started/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Blog::Application.config.session_store :cookie_store, key: '_blog_session' +Rails.application.config.session_store :cookie_store, key: '_blog_session' diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb index 0155b613a3..65d273b58d 100644 --- a/guides/code/getting_started/config/routes.rb +++ b/guides/code/getting_started/config/routes.rb @@ -1,4 +1,4 @@ -Blog::Application.routes.draw do +Rails.application.routes.draw do resources :posts do resources :comments end diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index dd81ec58f9..2d4be0cda7 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -294,7 +294,7 @@ NOTE. The old style `map` commands still work as before with a backwards compati Deprecations * The catch all route for non-REST applications (`/:controller/:action/:id`) is now commented out. -* Routes :path\_prefix no longer exists and :name\_prefix now automatically adds "\_" at the end of the given value. +* Routes `:path_prefix` no longer exists and `:name_prefix` now automatically adds "_" at the end of the given value. More Information: * [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/) diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index ce811a583b..cdcde67869 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -187,7 +187,7 @@ Action Pack Rails will use `layouts/single_car` when a request comes in `:show` action, and use `layouts/application` (or `layouts/cars`, if exists) when a request comes in for any other actions. -* `form\_for` is changed to use `#{action}\_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}\_#{action}`. +* `form_for` is changed to use `#{action}_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}_#{action}`. * `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`. diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 9fd930963a..7e39f761f2 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -10,11 +10,14 @@ </p> <% else %> <p> - These are the new guides for Rails 4.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 4.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. </p> <% end %> <p> + The guides for Rails 4.0.x are available at <a href="http://guides.rubyonrails.org/v4.0.4/">http://guides.rubyonrails.org/v4.0.4/</a>. +</p> +<p> The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.17/">http://guides.rubyonrails.org/v3.2.17/</a>. </p> <p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 0f46ba8698..ee2b00aedb 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -34,7 +34,7 @@ The naming convention of controllers in Rails favors pluralization of the last w Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details. -NOTE: The controller naming convention differs from the naming convention of models, which expected to be named in singular form. +NOTE: The controller naming convention differs from the naming convention of models, which are expected to be named in singular form. Methods and Actions @@ -364,21 +364,21 @@ If you need a different session storage mechanism, you can change it in the `con # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rails g active_record:session_migration") -# YourApp::Application.config.session_store :active_record_store +# Rails.application.config.session_store :active_record_store ``` Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`: ```ruby # Be sure to restart your server when you modify this file. -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session' +Rails.application.config.session_store :cookie_store, key: '_your_app_session' ``` You can also pass a `:domain` key and specify the domain name for the cookie: ```ruby # Be sure to restart your server when you modify this file. -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" +Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" ``` Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml` diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 667433285f..fbcce325ed 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -92,6 +92,7 @@ Here is a list with all the available Active Record callbacks, listed in the sam * `around_create` * `after_create` * `after_save` +* `after_commit/after_rollback` ### Updating an Object @@ -103,12 +104,14 @@ Here is a list with all the available Active Record callbacks, listed in the sam * `around_update` * `after_update` * `after_save` +* `after_commit/after_rollback` ### Destroying an Object * `before_destroy` * `around_destroy` * `after_destroy` +* `after_commit/after_rollback` WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 0a332d7dd9..2a76df156c 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1231,6 +1231,35 @@ Using a class method is the preferred way to accept arguments for scopes. These category.posts.created_before(time) ``` +### Applying a default scope + +If we wish for a scope to be applied across all queries to the model we can use the +`default_scope` method within the model itself. + +```ruby +class Client < ActiveRecord::Base + default_scope { where("removed_at IS NULL") } +end +``` + +When queries are executed on this model, the SQL query will now look something like +this: + +```sql +SELECT * FROM clients WHERE removed_at IS NULL +``` + +If you need to do more complex things with a default scope, you can alternatively +define it as a class method: + +```ruby +class Client < ActiveRecord::Base + def self.default_scope + # Should return an ActiveRecord::Relation. + end +end +``` + ### Merging of scopes Just like `where` clauses scopes are merged using `AND` conditions. @@ -1284,36 +1313,6 @@ User.where(state: 'inactive') As you can see above the `default_scope` is being merged in both `scope` and `where` conditions. - -### Applying a default scope - -If we wish for a scope to be applied across all queries to the model we can use the -`default_scope` method within the model itself. - -```ruby -class Client < ActiveRecord::Base - default_scope { where("removed_at IS NULL") } -end -``` - -When queries are executed on this model, the SQL query will now look something like -this: - -```sql -SELECT * FROM clients WHERE removed_at IS NULL -``` - -If you need to do more complex things with a default scope, you can alternatively -define it as a class method: - -```ruby -class Client < ActiveRecord::Base - def self.default_scope - # Should return an ActiveRecord::Relation. - end -end -``` - ### Removing All Scoping If we wish to remove scoping for any reason we can use the `unscoped` method. This is diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index aa844f8fd1..87d780eca9 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -964,20 +964,7 @@ NOTE: Defined in `active_support/core_ext/module/delegation.rb` There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either. -The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API: - -```ruby -redefine_method("#{reflection.name}=") do |new_value| - association = association_instance_get(reflection.name) - - if association.nil? || association.target != new_value - association = association_proxy_class.new(self, reflection) - end - - association.replace(new_value) - association_instance_set(reflection.name, new_value.nil? ? nil : association) -end -``` +The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. NOTE: Defined in `active_support/core_ext/module/remove_method.rb` @@ -1644,6 +1631,9 @@ Given a string with a qualified constant name, `demodulize` returns the very con "Product".demodulize # => "Product" "Backoffice::UsersController".demodulize # => "UsersController" "Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils" +"::Inflections".demodulize # => "Inflections" +"".demodulize # => "" + ``` Active Record for example uses this method to compute the name of a counter cache column: diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index dfc690971b..52fc9726d9 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -60,7 +60,7 @@ in `production.rb` - `config.assets.css_compressor` for your CSS and ```ruby config.assets.css_compressor = :yui -config.assets.js_compressor = :uglify +config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included @@ -245,7 +245,7 @@ When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it. The default locations are: the `images`, `javascripts` and `stylesheets` -directories under the `apps/assets` folder, but these subdirectories +directories under the `app/assets` folder, but these subdirectories are not special - any path under `assets/*` will be searched. For example, these files: @@ -581,23 +581,8 @@ runtime. To disable this behavior you can set: config.assets.raise_runtime_errors = false ``` -When `raise_runtime_errors` is set to `false` sprockets will not check that dependencies of assets are declared properly. Here is a scenario where you must tell the asset pipeline about a dependency: - -If you have `application.css.erb` that references `logo.png` like this: - -```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. - +When this option is true asset pipeline will check if all the assets loaded in your application +are included in the `config.assets.precompile` list. ### Turning Debugging Off @@ -724,17 +709,17 @@ JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS. If you have other manifests or individual stylesheets and JavaScript files to -include, you can add them to the `precompile` array in `config/application.rb`: +include, you can add them to the `precompile` array in `config/initializers/assets.rb`: ```ruby -config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] +Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] ``` Or, you can opt to precompile all assets with something like this: ```ruby -# config/application.rb -config.assets.precompile << Proc.new do |path| +# config/initializers/assets.rb +Rails.application.config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path @@ -1056,6 +1041,14 @@ cache store. config.assets.cache_store = :memory_store, { size: 32.megabytes } ``` +To disable the assets cache store: + +```ruby +config.assets.configure do |env| + env.cache = ActiveSupport::Cache.lookup_store(:null_store) +end +``` + Adding Assets to Your Gems -------------------------- diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 5ec6ae0f21..df38bd7321 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -571,7 +571,7 @@ If you create an association some time after you build the underlying model, you If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering. -WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper\_boxes" and "papers" to generate a join table name of "papers\_paper\_boxes" because of the length of the name "paper\_boxes", but it in fact generates a join table name of "paper\_boxes\_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings). +WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings). Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations: diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index e898d75d1a..b6423dd44e 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -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). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Action Caching Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy. -INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Fragment Caching diff --git a/guides/source/configuring.md b/guides/source/configuring.md index b17c24363b..805f4bb81f 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -580,13 +580,13 @@ The only way to explicitly not use the connection information in `ENV['DATABASE_ ``` $ cat config/database.yml development: - url: sqlite3://localhost/NOT_my_database + url: sqlite3:NOT_my_database $ echo $DATABASE_URL postgresql://localhost/my_database $ rails runner 'puts ActiveRecord::Base.connections' -{"development"=>{"adapter"=>"sqlite3", "host"=>"localhost", "database"=>"NOT_my_database"}} +{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}} ``` Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name. diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 7c6858fa2c..8767fbecce 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <% end %> <%= author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails developer at <a href="http://www.37signals.com">37signals</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. + Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. <% end %> <%= author('Emilio Tagua', 'miloops') do %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 0e10d1b697..b067d9efb7 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -210,7 +210,7 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs " ``` ### Impact of Logs on Performance -Logging will always have a small impact on performance of your rails app, +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`, @@ -224,446 +224,576 @@ Another potential pitfall is that if you have many calls to `Logger` like this logger.debug "Person attributes hash: #{@person.attributes.inspect}" ``` -In the above example, There will be a performance impact even if the allowed -output level doesn't include debug. The reason is that Ruby has to evaluate -these strings, which includes instantiating the somewhat heavy `String` object +In the above example, There will be a performance impact even if the allowed +output level doesn't include debug. The reason is that Ruby has to evaluate +these strings, which includes instantiating the somewhat heavy `String` object and interpolating the variables, and which takes time. -Therefore, it's recommended to pass blocks to the logger methods, as these are -only evaluated if the output level is the same or included in the allowed level +Therefore, it's recommended to pass blocks to the logger methods, as these are +only evaluated if the output level is the same or included in the allowed level (i.e. lazy loading). The same code rewritten would be: ```ruby logger.debug {"Person attributes hash: #{@person.attributes.inspect}"} ``` -The contents of the block, and therefore the string interpolation, is only -evaluated if debug is enabled. This performance savings is only really +The contents of the block, and therefore the string interpolation, is only +evaluated if debug is enabled. This performance savings is only really noticeable with large amounts of logging, but it's a good practice to employ. -Debugging with the `debugger` gem +Debugging with the `byebug` gem --------------------------------- -When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion. +When your code is behaving in unexpected ways, you can try printing to logs or +the console to diagnose the problem. Unfortunately, there are times when this +sort of error tracking is not effective in finding the root cause of a problem. +When you actually need to journey into your running source code, the debugger +is your best companion. -The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written deeper into Rails code. +The debugger can also help you if you want to learn about the Rails source code +but don't know where to start. Just debug any request to your application and +use this guide to learn how to move from the code you have written deeper into +Rails code. ### Setup -You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run: +You can use the `byebug` gem to set breakpoints and step through live code in +Rails. To install it, just run: ```bash -$ gem install debugger +$ gem install byebug ``` -Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the `debugger` method. +Inside any Rails application you can then invoke the debugger by calling the +`byebug` method. Here's an example: ```ruby class PeopleController < ApplicationController def new - debugger + byebug @person = Person.new end end ``` -If you see this message in the console or logs: +### The Shell + +As soon as your application calls the `byebug` method, the debugger will be +started in a debugger shell inside the terminal window where you launched your +application server, and you will be placed at the debugger's prompt `(byebug)`. +Before the prompt, the code around the line that is about to be run will be +displayed and the current line will be marked by '=>'. Like this: ``` -***** Debugger requested, but was not available: Start server with --debugger to enable ***** +[1, 10] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } + +(byebug) ``` -Make sure you have started your web server with the option `--debugger`: +If you got there by a browser request, the browser tab containing the request +will be hung until the debugger has finished and the trace has finished +processing the entire request. + +For example: ```bash -$ rails server --debugger => Booting WEBrick -=> Rails 4.0.0 application starting on http://0.0.0.0:3000 -=> Debugger enabled -... -``` +=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000 +=> Run `rails server -h` for more startup options +=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option) +=> Ctrl-C to shutdown server +[2014-04-11 13:11:47] INFO WEBrick 1.3.1 +[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux] +[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000 -TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, even if it was started without `--debugger`. -### The Shell +Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200 + ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations" +Processing by PostsController#index as HTML -As soon as your application calls the `debugger` method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt `(rdb:n)`. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run. +[3, 12] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } -If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request. +(byebug) +``` -For example: +Now it's time to explore and dig into your application. A good place to start is +by asking the debugger for help. Type: `help` -```bash -@posts = Post.all -(rdb:7) ``` +(byebug) help -Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help` +byebug 2.7.0 -``` -(rdb:7) help -ruby-debug help v0.10.2 Type 'help <command-name>' for help on a specific command Available commands: -backtrace delete enable help next quit show trace -break disable eval info p reload source undisplay -catch display exit irb pp restart step up -condition down finish list ps save thread var -continue edit frame method putl set tmate where +backtrace delete enable help list pry next restart source up +break disable eval info method ps save step var +catch display exit interrupt next putl set thread +condition down finish irb p quit show trace +continue edit frame kill pp reload skip undisplay ``` -TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_ - -The next command to learn is one of the most useful: `list`. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use `l` for the `list` command. +TIP: To view the help menu for any command use `help <command-name>` at the +debugger prompt. For example: _`help list`_. You can abbreviate any debugging +command by supplying just enough letters to distinguish them from other +commands, so you can also use `l` for the `list` command, for example. -This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by `=>`. +To see the previous ten lines you should type `list-` (or `l-`) ``` -(rdb:7) list +(byebug) l- + [1, 10] in /PathTo/project/app/controllers/posts_controller.rb 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } -``` + 2 before_action :set_post, only: [:show, :edit, :update, :destroy] + 3 + 4 # GET /posts + 5 # GET /posts.json + 6 def index + 7 byebug + 8 @posts = Post.find_recent + 9 + 10 respond_to do |format| -If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out. - -``` -(rdb:7) l -[11, 20] in /PathTo/project/app/controllers/posts_controller.rb - 11 end - 12 end - 13 - 14 # GET /posts/1 - 15 # GET /posts/1.json - 16 def show - 17 @post = Post.find(params[:id]) - 18 - 19 respond_to do |format| - 20 format.html # show.html.erb ``` -And so on until the end of the current file. When the end of file is reached, the `list` command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer. +This way you can move inside the file, being able to see the code above and over +the line where you added the `byebug` call. Finally, to see where you are in +the code again you can type `list=` -On the other hand, to see the previous ten lines you should type `list-` (or `l-`) - -``` -(rdb:7) l- -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger - 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } ``` +(byebug) list= -This way you can move inside the file, being able to see the code above and over the line you added the `debugger`. -Finally, to see where you are in the code again you can type `list=` +[3, 12] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } -``` -(rdb:7) list= -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } +(byebug) ``` ### The Context -When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack. - -The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. - -At any time you can call the `backtrace` command (or its alias `where`) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then `backtrace` will supply the answer. - -``` -(rdb:5) where - #0 PostsController.index - at line /PathTo/project/app/controllers/posts_controller.rb:6 - #1 Kernel.send - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...) - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617 +When you start debugging your application, you will be placed in different +contexts as you go through the different parts of the stack. + +The debugger creates a context when a stopping point or an event is reached. The +context has information about the suspended program which enables the debugger +to inspect the frame stack, evaluate variables from the perspective of the +debugged program, and contains information about the place where the debugged +program is stopped. + +At any time you can call the `backtrace` command (or its alias `where`) to print +the backtrace of the application. This can be very helpful to know how you got +where you are. If you ever wondered about how you got somewhere in your code, +then `backtrace` will supply the answer. + +``` +(byebug) where +--> #0 PostsController.index + at /PathTo/project/test_app/app/controllers/posts_controller.rb:8 + #1 ActionController::ImplicitRender.send_action(method#String, *args#Array) + at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/implicit_render.rb:4 + #2 AbstractController::Base.process_action(action#NilClass, *args#Array) + at /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb:189 + #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass) + at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/rendering.rb:10 ... ``` -You move anywhere you want in this trace (thus changing the context) by using the `frame _n_` command, where _n_ is the specified frame number. +The current frame is marked with `-->`. You can move anywhere you want in this +trace (thus changing the context) by using the `frame _n_` command, where _n_ is +the specified frame number. If you do that, `byebug` will display your new +context. ``` -(rdb:5) frame 2 -#2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 +(byebug) frame 2 + +[184, 193] in /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb + 184: # is the intended way to override action dispatching. + 185: # + 186: # Notice that the first argument is the method to be dispatched + 187: # which is *not* necessarily the same as the action name. + 188: def process_action(method_name, *args) +=> 189: send_action(method_name, *args) + 190: end + 191: + 192: # Actually call the method associated with the action. Override + 193: # this method if you wish to change how action methods are called, + +(byebug) ``` -The available variables are the same as if you were running the code line by line. After all, that's what debugging is. +The available variables are the same as if you were running the code line by +line. After all, that's what debugging is. -Moving up and down the stack frame: You can use `up [n]` (`u` for abbreviated) and `down [n]` commands in order to change the context _n_ frames up or down the stack respectively. _n_ defaults to one. Up in this case is towards higher-numbered stack frames, and down is towards lower-numbered stack frames. +You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order +to change the context _n_ frames up or down the stack respectively. _n_ defaults +to one. Up in this case is towards higher-numbered stack frames, and down is +towards lower-numbered stack frames. ### Threads -The debugger can list, stop, resume and switch between running threads by using the command `thread` (or the abbreviated `th`). This command has a handful of options: +The debugger can list, stop, resume and switch between running threads by using +the `thread` command (or the abbreviated `th`). This command has a handful of +options: * `thread` shows the current thread. -* `thread list` is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution. +* `thread list` is used to list all threads and their statuses. The plus + +character and the number indicates the current thread of execution. * `thread stop _n_` stop thread _n_. * `thread resume _n_` resumes thread _n_. * `thread switch _n_` switches the current thread context to _n_. -This command is very helpful, among other occasions, when you are debugging concurrent threads and need to verify that there are no race conditions in your code. +This command is very helpful, among other occasions, when you are debugging +concurrent threads and need to verify that there are no race conditions in your +code. ### Inspecting Variables -Any expression can be evaluated in the current context. To evaluate an expression, just type it! - -This example shows how you can print the instance_variables defined within the current context: - -``` -@posts = Post.all -(rdb:11) instance_variables -["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"] -``` - -As you may have figured out, all of the variables that you can access from a controller are displayed. This list is dynamically updated as you execute code. For example, run the next line using `next` (you'll learn more about this command later in this guide). - -``` -(rdb:11) next -Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET] - Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e - Parameters: {"action"=>"index", "controller"=>"posts"} -/PathToProject/posts_controller.rb:8 -respond_to do |format| +Any expression can be evaluated in the current context. To evaluate an +expression, just type it! + +This example shows how you can print the instance variables defined within the +current context: + +``` +[3, 12] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } + +(byebug) instance_variables +[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, + :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name, + :@_response_body, :@marked_for_same_origin_verification, :@_config] +``` + +As you may have figured out, all of the variables that you can access from a +controller are displayed. This list is dynamically updated as you execute code. +For example, run the next line using `next` (you'll learn more about this +command later in this guide). + +``` +(byebug) next +[5, 14] in /PathTo/project/app/controllers/posts_controller.rb + 5 # GET /posts.json + 6 def index + 7 byebug + 8 @posts = Post.find_recent + 9 +=> 10 respond_to do |format| + 11 format.html # index.html.erb + 12 format.json { render json: @posts } + 13 end + 14 end + 15 +(byebug) ``` And then ask again for the instance_variables: ``` -(rdb:11) instance_variables.include? "@posts" +(byebug) instance_variables.include? "@posts" true ``` -Now `@posts` is included in the instance variables, because the line defining it was executed. +Now `@posts` is included in the instance variables, because the line defining it +was executed. -TIP: You can also step into **irb** mode with the command `irb` (of course!). This way an irb session will be started within the context you invoked it. But be warned: this is an experimental feature. +TIP: You can also step into **irb** mode with the command `irb` (of course!). +This way an irb session will be started within the context you invoked it. But +be warned: this is an experimental feature. -The `var` method is the most convenient way to show variables and their values: +The `var` method is the most convenient way to show variables and their values. +Let's let `byebug` to help us with it. ``` -var -(rdb:1) v[ar] const <object> show constants of object -(rdb:1) v[ar] g[lobal] show global variables -(rdb:1) v[ar] i[nstance] <object> show instance variables of object -(rdb:1) v[ar] l[ocal] show local variables +(byebug) help var +v[ar] cl[ass] show class variables of self +v[ar] const <object> show constants of object +v[ar] g[lobal] show global variables +v[ar] i[nstance] <object> show instance variables of object +v[ar] l[ocal] show local variables ``` -This is a great way to inspect the values of the current context variables. For example: +This is a great way to inspect the values of the current context variables. For +example, to check that we have no local variables currently defined. ``` -(rdb:9) var local - __dbg_verbose_save => false +(byebug) var local +(byebug) ``` You can also inspect for an object method this way: ``` -(rdb:9) var instance Post.new -@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"... +(byebug) var instance Post.new +@_start_transaction_state = {} +@aggregation_cache = {} +@association_cache = {} +@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil} @attributes_cache = {} -@new_record = true +@changed_attributes = nil +... ``` -TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate Ruby expressions and display the value of variables to the console. +TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate +Ruby expressions and display the value of variables to the console. -You can use also `display` to start watching variables. This is a good way of tracking the values of a variable while the execution goes on. +You can use also `display` to start watching variables. This is a good way of +tracking the values of a variable while the execution goes on. ``` -(rdb:1) display @recent_comments -1: @recent_comments = +(byebug) display @posts +1: @posts = nil ``` -The variables inside the displaying list will be printed with their values after you move in the stack. To stop displaying a variable use `undisplay _n_` where _n_ is the variable number (1 in the last example). +The variables inside the displaying list will be printed with their values after +you move in the stack. To stop displaying a variable use `undisplay _n_` where +_n_ is the variable number (1 in the last example). ### Step by Step -Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution. +Now you should know where you are in the running trace and be able to print the +available variables. But lets continue and move on with the application +execution. -Use `step` (abbreviated `s`) to continue running your program until the next logical stopping point and return control to the debugger. +Use `step` (abbreviated `s`) to continue running your program until the next +logical stopping point and return control to the debugger. -TIP: You can also use `step+ n` and `step- n` to move forward or backward `n` steps respectively. +You may also use `next` which is similar to step, but function or method calls +that appear within the line of code are executed without stopping. -You may also use `next` which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps. +TIP: You can also use `step n` or `next n` to move forwards `n` steps at once. -The difference between `next` and `step` is that `step` stops at the next line of code executed, doing just a single step, while `next` moves to the next line without descending inside methods. +The difference between `next` and `step` is that `step` stops at the next line +of code executed, doing just a single step, while `next` moves to the next line +without descending inside methods. -For example, consider this block of code with an included `debugger` statement: +For example, consider the following situation: ```ruby -class Author < ActiveRecord::Base - has_one :editorial - has_many :comments +Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200 +Processing by PostsController#index as HTML - def find_recent_comments(limit = 10) - debugger - @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - end -end +[1, 8] in /home/davidr/Proyectos/test_app/app/models/post.rb + 1: class Post < ActiveRecord::Base + 2: + 3: def self.find_recent(limit = 10) + 4: byebug +=> 5: where('created_at > ?', 1.week.ago).limit(limit) + 6: end + 7: + 8: end + +(byebug) ``` -TIP: You can use the debugger while using `rails console`. Just remember to `require "debugger"` before calling the `debugger` method. +If we use `next`, we want go deep inside method calls. Instead, byebug will go +to the next line within the same context. In this case, this is the last line of +the method, so `byebug` will jump to next next line of the previous frame. ``` -$ rails console -Loading development environment (Rails 4.0.0) ->> require "debugger" -=> [] ->> author = Author.first -=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10"> ->> author.find_recent_comments -/PathTo/project/app/models/author.rb:11 -) -``` +(byebug) next +Next went up a frame because previous frame finished -With the code stopped, take a look around: +[4, 13] in /PathTo/project/test_app/app/controllers/posts_controller.rb + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: @posts = Post.find_recent + 8: +=> 9: respond_to do |format| + 10: format.html # index.html.erb + 11: format.json { render json: @posts } + 12: end + 13: end -``` -(rdb:1) list -[2, 9] in /PathTo/project/app/models/author.rb - 2 has_one :editorial - 3 has_many :comments - 4 - 5 def find_recent_comments(limit = 10) - 6 debugger -=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - 8 end - 9 end +(byebug) ``` -You are at the end of the line, but... was this line executed? You can inspect the instance variables. +If we use `step` in the same situation, we will literally go the next ruby +instruction to be executed. In this case, the activesupport's `week` method. ``` -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -``` +(byebug) step -`@recent_comments` hasn't been defined yet, so it's clear that this line hasn't been executed yet. Use the `next` command to move on in the code: +[50, 59] in /PathToGems/activesupport-4.1.0/lib/active_support/core_ext/numeric/time.rb + 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) + 51: end + 52: alias :day :days + 53: + 54: def weeks +=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) + 56: end + 57: alias :week :weeks + 58: + 59: def fortnights +(byebug) ``` -(rdb:1) next -/PathTo/project/app/models/author.rb:12 -@recent_comments -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -@comments = [] -@recent_comments = [] -``` - -Now you can see that the `@comments` relationship was loaded and @recent_comments defined because the line was executed. -If you want to go deeper into the stack trace you can move single `steps`, through your calling methods and into Rails code. This is one of the best ways to find bugs in your code, or perhaps in Ruby or Rails. +This is one of the best ways to find bugs in your code, or perhaps in Ruby on +Rails. ### Breakpoints -A breakpoint makes your application stop whenever a certain point in the program is reached. The debugger shell is invoked in that line. +A breakpoint makes your application stop whenever a certain point in the program +is reached. The debugger shell is invoked in that line. -You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually: +You can add breakpoints dynamically with the command `break` (or just `b`). +There are 3 possible ways of adding breakpoints manually: * `break line`: set breakpoint in the _line_ in the current source file. -* `break file:line [if expression]`: set breakpoint in the _line_ number inside the _file_. If an _expression_ is given it must evaluated to _true_ to fire up the debugger. -* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The _expression_ works the same way as with file:line. +* `break file:line [if expression]`: set breakpoint in the _line_ number inside +the _file_. If an _expression_ is given it must evaluated to _true_ to fire up +the debugger. +* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and +\# for class and instance method respectively) defined in _class_. The +_expression_ works the same way as with file:line. + + +For example, in the previous situation ``` -(rdb:5) break 10 -Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10 +[4, 13] in /PathTo/project/app/controllers/posts_controller.rb + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: @posts = Post.find_recent + 8: +=> 9: respond_to do |format| + 10: format.html # index.html.erb + 11: format.json { render json: @posts } + 12: end + 13: end + +(byebug) break 11 +Created breakpoint 1 at /PathTo/project/app/controllers/posts_controller.rb:11 + ``` -Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. +Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you +supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. ``` -(rdb:5) info breakpoints +(byebug) info breakpoints Num Enb What - 1 y at filters.rb:10 +1 y at /PathTo/project/app/controllers/posts_controller.rb:11 ``` -To delete breakpoints: use the command `delete _n_` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active.. +To delete breakpoints: use the command `delete _n_` to remove the breakpoint +number _n_. If no number is specified, it deletes all breakpoints that are +currently active. ``` -(rdb:5) delete 1 -(rdb:5) info breakpoints +(byebug) delete 1 +(byebug) info breakpoints No breakpoints. ``` You can also enable or disable breakpoints: -* `enable breakpoints`: allow a list _breakpoints_ or all of them if no list is specified, to stop your program. This is the default state when you create a breakpoint. +* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is +specified, to stop your program. This is the default state when you create a +breakpoint. * `disable breakpoints`: the _breakpoints_ will have no effect on your program. ### Catching Exceptions -The command `catch exception-name` (or just `cat exception-name`) can be used to intercept an exception of type _exception-name_ when there would otherwise be is no handler for it. +The command `catch exception-name` (or just `cat exception-name`) can be used to +intercept an exception of type _exception-name_ when there would otherwise be no +handler for it. To list all active catchpoints use `catch`. ### Resuming Execution -There are two ways to resume execution of an application that is stopped in the debugger: - -* `continue` [line-specification] \(or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached. -* `finish` [frame-number] \(or `fin`): execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given it will run until the specified frame returns. +There are two ways to resume execution of an application that is stopped in the +debugger: + +* `continue` [line-specification] \(or `c`): resume program execution, at the +address where your script last stopped; any breakpoints set at that address are +bypassed. The optional argument line-specification allows you to specify a line +number to set a one-time breakpoint which is deleted when that breakpoint is +reached. +* `finish` [frame-number] \(or `fin`): execute until the selected stack frame +returns. If no frame number is given, the application will run until the +currently selected frame returns. The currently selected frame starts out the +most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been +performed. If a frame number is given it will run until the specified frame +returns. ### Editing Two commands allow you to open code from the debugger into an editor: -* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR environment variable. A specific _line_ can also be given. -* `tmate _n_` (abbreviated `tm`): open the current file in TextMate. It uses n-th frame if _n_ is specified. +* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR +environment variable. A specific _line_ can also be given. ### Quitting -To exit the debugger, use the `quit` command (abbreviated `q`), or its alias `exit`. +To exit the debugger, use the `quit` command (abbreviated `q`), or its alias +`exit`. -A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again. +A simple quit tries to terminate all threads in effect. Therefore your server +will be stopped and you will have to start it again. ### Settings -The `debugger` gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options: - -* `set reload`: Reload source code when changed. -* `set autolist`: Execute `list` command on every breakpoint. -* `set listsize _n_`: Set number of source lines to list by default to _n_. -* `set forcestep`: Make sure the `next` and `step` commands always move to a new line +`byebug` has a few available options to tweak its behaviour: -You can see the full list by using `help set`. Use `help set _subcommand_` to learn about a particular `set` command. +* `set autoreload`: Reload source code when changed (default: true). +* `set autolist`: Execute `list` command on every breakpoint (default: true). +* `set listsize _n_`: Set number of source lines to list by default to _n_ +(default: 10) +* `set forcestep`: Make sure the `next` and `step` commands always move to a new +line. -TIP: You can save these settings in an `.rdebugrc` file in your home directory. The debugger reads these global settings when it starts. +You can see the full list by using `help set`. Use `help set _subcommand_` to +learn about a particular `set` command. -Here's a good start for an `.rdebugrc`: +TIP: You can save these settings in an `.byebugrc` file in your home directory. +The debugger reads these global settings when it starts. For example: ```bash -set autolist set forcestep set listsize 25 ``` @@ -671,35 +801,59 @@ set listsize 25 Debugging Memory Leaks ---------------------- -A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level. +A Ruby application (on Rails or not), can leak memory - either in the Ruby code +or at the C code level. -In this section, you will learn how to find and fix such leaks by using tool such as Valgrind. +In this section, you will learn how to find and fix such leaks by using tool +such as Valgrind. ### Valgrind -[Valgrind](http://valgrind.org/) is a Linux-only application for detecting C-based memory leaks and race conditions. +[Valgrind](http://valgrind.org/) is a Linux-only application for detecting +C-based memory leaks and race conditions. -There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C extension in the interpreter calls `malloc()` but doesn't properly call `free()`, this memory won't be available until the app terminates. +There are Valgrind tools that can automatically detect many memory management +and threading bugs, and profile your programs in detail. For example, if a C +extension in the interpreter calls `malloc()` but doesn't properly call +`free()`, this memory won't be available until the app terminates. -For further information on how to install Valgrind and use with Ruby, refer to [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) by Evan Weaver. +For further information on how to install Valgrind and use with Ruby, refer to +[Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) +by Evan Weaver. Plugins for Debugging --------------------- -There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging: - -* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate. -* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs. -* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed. -* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application. -* [Better Errors](https://github.com/charliesome/better_errors) Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection. -* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser - in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. +There are some Rails plugins to help you to find errors and debug your +application. Here is a list of useful plugins for debugging: + +* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has +footnotes that give request information and link back to your source via +TextMate. +* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query +origin tracing to your logs. +* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin +not only runs "EXPLAIN" before each of your select queries in development, but +provides a small DIV in the rendered output of each page with the summary of +warnings for each query that it analyzed. +* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) +Provides a mailer object and a default set of templates for sending email +notifications when errors occur in a Rails application. +* [Better Errors](https://github.com/charliesome/better_errors) Replaces the +standard Rails error page with a new one containing more contextual information, +like source code and variable inspection. +* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails +development that will end your tailing of development.log. Have all information +about your Rails app requests in the browser - in the Developer Tools panel. +Provides insight to db/rendering/total times, parameter list, rendered views and +more. References ---------- * [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html) * [debugger Homepage](https://github.com/cldwalker/debugger) +* [byebug Homepage](https://github.com/deivid-rodriguez/byebug) * [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/) * [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised) * [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace) diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 4ee43b6a97..b0e070120d 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -117,7 +117,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. -You can use homebrew to install memcached on OSX: +You can use [Homebrew](http://brew.sh/) to install memcached on OSX: ```bash $ brew install memcached @@ -210,6 +210,14 @@ FreeBSD users will have to run the following: # pkg_add -r postgresql92-client postgresql92-server ``` +You can use [Homebrew](http://brew.sh/) to install MySQL and PostgreSQL on OSX: + +```bash +$ brew install mysql +$ brew install postgresql +``` +Follow instructions given by [Homebrew](http://brew.sh/) to start these. + 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). @@ -245,10 +253,15 @@ $ bundle exec rake mysql:build_databases ``` PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account +This is not needed when installed via [Homebrew](http://brew.sh). ```bash $ sudo -u postgres createuser --superuser $USER ``` +And for OS X (when installed via [Homebrew](http://brew.sh)) +```bash +$ createuser --superuser $USER +``` and then create the test databases with diff --git a/guides/source/engines.md b/guides/source/engines.md index bbd63bb892..8f9ba0995f 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1052,6 +1052,16 @@ 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. +Another way to do this is to assign the `@routes` instance variable to `Engine.routes` in your test setup: + +```ruby +setup do + @routes = Engine.routes +end +``` + +This will also ensure url helpers for the engine will work as expected in your tests. + Improving engine functionality ------------------------------ diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index c54c9efe94..36bbd1187c 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -99,7 +99,7 @@ ruby 2.0.0p353 ``` If you don't have Ruby installed have a look at -[ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to +[ruby-lang.org](https://www.ruby-lang.org/en/installation/) for possible ways to install Ruby on your platform. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows @@ -344,7 +344,7 @@ resource. Here's what `config/routes.rb` should look like after the _article resource_ is declared. ```ruby -Blog::Application.routes.draw do +Rails.application.routes.draw do resources :articles @@ -612,7 +612,7 @@ def create end ``` -The `render` method here is taking a very simple hash with a key of `text` and +The `render` method here is taking a very simple hash with a key of `plain` and value of `params[:article].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which @@ -1136,7 +1136,7 @@ The `method: :patch` option tells Rails that we want this form to be submitted via the `PATCH` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. -The first parameter of the `form_tag` can be an object, say, `@article` which would +The first parameter of `form_for` can be an object, say, `@article` which would cause the helper to fill in the form with the fields of the object. Passing in a symbol (`:article`) with the same name as the instance variable (`@article`) also automagically leads to the same behavior. This is what is happening here. More details diff --git a/guides/source/i18n.md b/guides/source/i18n.md index bef738b75b..c1b575c7b7 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -92,7 +92,7 @@ Rails adds all `.rb` and `.yml` files from the `config/locales` directory to you The default `en.yml` locale in this directory contains a sample pair of translation strings: -```ruby +```yaml en: hello: "Hello world" ``` @@ -179,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.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end ``` @@ -192,7 +192,7 @@ We can also set the locale from the _subdomain_ in a very similar way: # in your /etc/hosts file to try this out locally def extract_locale_from_subdomain parsed_locale = request.subdomains.first - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end ``` @@ -212,17 +212,16 @@ The most usual way of setting (and passing) the locale would be to include it in This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though. -Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(locale: I18n.locale))`) would be tedious and probably impossible, of course. +Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(locale: I18n.locale))`, would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our `ApplicationController` then: ```ruby # app/controllers/application_controller.rb -def default_url_options(options={}) - logger.debug "default_url_options is passed options: #{options.inspect}\n" - { locale: I18n.locale } +def default_url_options(options = {}) + { locale: I18n.locale }.merge options end ``` @@ -310,7 +309,7 @@ You most probably have something like this in one of your applications: ```ruby # config/routes.rb -Yourapp::Application.routes.draw do +Rails.application.routes.draw do root to: "home#index" end ``` @@ -370,7 +369,7 @@ NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do So let's add the missing translations into the dictionary files (i.e. do the "localization" part): -```ruby +```yaml # config/locales/en.yml en: hello_world: Hello world! @@ -422,7 +421,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali And in our pirate translations file let's add a time format (it's already there in Rails' defaults for English): -```ruby +```yaml # config/locales/pirate.yml pirate: time: @@ -681,62 +680,13 @@ NOTE: Automatic conversion to HTML safe translate text is only available from th ![i18n demo html safe](images/i18n/demo_html_safe.png) -How to Store your Custom Translations -------------------------------------- - -The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] - -For example a Ruby Hash providing translations can look like this: - -```ruby -{ - pt: { - foo: { - bar: "baz" - } - } -} -``` - -The equivalent YAML file would look like this: - -```ruby -pt: - foo: - bar: baz -``` - -As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". - -Here is a "real" example from the Active Support `en.yml` translations YAML file: - -```ruby -en: - date: - formats: - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" -``` - -So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: - -```ruby -I18n.t 'date.formats.short' -I18n.t 'formats.short', scope: :date -I18n.t :short, scope: 'date.formats' -I18n.t :short, scope: [:date, :formats] -``` - -Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. - ### Translations for Active Record Models You can use the methods `Model.model_name.human` and `Model.human_attribute_name(attribute)` to transparently look up translations for your model and attribute names. For example when you add the following translations: -```ruby +```yaml en: activerecord: models: @@ -751,7 +701,7 @@ Then `User.model_name.human` will return "Dude" and `User.human_attribute_name(" You can also set a plural form for model names, adding as following: -```ruby +```yaml en: activerecord: models: @@ -921,6 +871,55 @@ Rails uses fixed strings and other localizations, such as format strings and oth * `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope. +How to Store your Custom Translations +------------------------------------- + +The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] + +For example a Ruby Hash providing translations can look like this: + +```yaml +{ + pt: { + foo: { + bar: "baz" + } + } +} +``` + +The equivalent YAML file would look like this: + +```yaml +pt: + foo: + bar: baz +``` + +As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". + +Here is a "real" example from the Active Support `en.yml` translations YAML file: + +```yaml +en: + date: + formats: + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" +``` + +So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: + +```ruby +I18n.t 'date.formats.short' +I18n.t 'formats.short', scope: :date +I18n.t :short, scope: 'date.formats' +I18n.t :short, scope: [:date, :formats] +``` + +Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. + Customize your I18n Setup ------------------------- diff --git a/guides/source/initialization.md b/guides/source/initialization.md index ec3cec5c6f..77f3615ca0 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -166,6 +166,7 @@ is called. COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) def run_command!(command) + command = parse_command(command) if COMMAND_WHITELIST.include?(command) send(command) else @@ -178,8 +179,7 @@ With the `server` command, Rails will further run the following code: ```ruby def set_application_directory! - Dir.chdir(File.expand_path('../../', APP_PATH)) unless - File.exist?(File.expand_path("config.ru")) + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) end def server @@ -187,6 +187,8 @@ def server require_command!("server") Rails::Server.new.tap do |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. require APP_PATH Dir.chdir(Rails.application.root) server.start @@ -207,6 +209,7 @@ sets up the `Rails::Server` class. require 'fileutils' require 'optparse' require 'action_dispatch' +require 'rails' module Rails class Server < ::Rack::Server @@ -273,7 +276,7 @@ def parse_options(args) # http://www.meb.uni-bonn.de/docs/cgi/cl.html args.clear if ENV.include?("REQUEST_METHOD") - options.merge! opt_parser.parse! args + options.merge! opt_parser.parse!(args) options[:config] = ::File.expand_path(options[:config]) ENV["RACK_ENV"] = options[:environment] options @@ -284,13 +287,16 @@ With the `default_options` set to this: ```ruby def default_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + { - environment: ENV['RACK_ENV'] || "development", - pid: nil, - Port: 9292, - Host: "0.0.0.0", - AccessLog: [], - config: "config.ru" + :environment => environment, + :pid => nil, + :Port => 9292, + :Host => default_host, + :AccessLog => [], + :config => "config.ru" } end ``` @@ -348,6 +354,7 @@ private def print_boot_information ... puts "=> Run `rails server -h` for more startup options" + ... puts "=> Ctrl-C to shutdown server" unless options[:daemonize] end @@ -434,7 +441,11 @@ The `app` method here is defined like so: ```ruby def app - @app ||= begin + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config +end +... +private + def build_app_and_options_from_config if !::File.exist? options[:config] abort "configuration #{options[:config]} not found" end @@ -443,7 +454,10 @@ def app self.options.merge! options app end -end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end ``` The `options[:config]` value defaults to `config.ru` which contains this: @@ -459,8 +473,14 @@ run <%= app_const %> The `Rack::Builder.parse_file` method here takes the content from this `config.ru` file and parses it using this code: ```ruby -app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", - TOPLEVEL_BINDING, config +app = new_from_string cfgfile, config + +... + +def self.new_from_string(builder_script, file="(rackup)") + eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app", + TOPLEVEL_BINDING, file, 0 +end ``` The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run: @@ -473,11 +493,22 @@ require ::File.expand_path('../config/environment', __FILE__) This file is the common file required by `config.ru` (`rails server`) and Passenger. This is where these two ways to run the server meet; everything before this point has been Rack and Rails setup. -This file begins with requiring `config/application.rb`. +This file begins with requiring `config/application.rb`: + +```ruby +require File.expand_path('../application', __FILE__) +``` ### `config/application.rb` -This file requires `config/boot.rb`, but only if it hasn't been required before, which would be the case in `rails server` but **wouldn't** be the case with Passenger. +This file requires `config/boot.rb`: + +```ruby +require File.expand_path('../boot', __FILE__) +``` + +But only if it hasn't been required before, which would be the case in `rails server` +but **wouldn't** be the case with Passenger. Then the fun begins! @@ -498,11 +529,12 @@ This file is responsible for requiring all the individual frameworks of Rails: require "rails" %w( - active_record - action_controller - action_mailer - rails/test_unit - sprockets + active_record + action_controller + action_view + action_mailer + rails/test_unit + sprockets ).each do |framework| begin require "#{framework}/railtie" @@ -526,7 +558,7 @@ The rest of `config/application.rb` defines the configuration for the 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 +`Blog`, here we would find `Rails.application.initialize!`, which is defined in `rails/application.rb` ### `railties/lib/rails/application.rb` @@ -568,7 +600,7 @@ initializers (like building the middleware stack) are run last. The `railtie` initializers are the initializers which have been defined on the `Rails::Application` itself and are run between the `bootstrap` and `finishers`. -After this is done we go back to `Rack::Server` +After this is done we go back to `Rack::Server`. ### Rack: lib/rack/server.rb @@ -576,7 +608,11 @@ Last time we left when the `app` method was being defined: ```ruby def app - @app ||= begin + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config +end +... +private + def build_app_and_options_from_config if !::File.exist? options[:config] abort "configuration #{options[:config]} not found" end @@ -585,7 +621,10 @@ def app self.options.merge! options app end -end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end ``` At this point `app` is the Rails app itself (a middleware), and what @@ -611,40 +650,50 @@ server.run wrapped_app, options, &blk ``` At this point, the implementation of `server.run` will depend on the -server you're using. For example, if you were using Mongrel, here's what +server you're using. For example, if you were using Puma, here's what the `run` method would look like: ```ruby -def self.run(app, options={}) - server = ::Mongrel::HttpServer.new( - options[:Host] || '0.0.0.0', - options[:Port] || 8080, - options[:num_processors] || 950, - options[:throttle] || 0, - options[:timeout] || 60) - # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. - # Use is similar to #run, replacing the app argument with a hash of - # { path=>app, ... } or an instance of Rack::URLMap. - if options[:map] - if app.is_a? Hash - app.each do |path, appl| - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - elsif app.is_a? URLMap - app.instance_variable_get(:@mapping).each do |(host, path, appl)| - next if !host.nil? && !options[:Host].nil? && options[:Host] != host - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - else - raise ArgumentError, "first argument should be a Hash or URLMap" - end - else - server.register('/', Rack::Handler::Mongrel.new(app)) +... +DEFAULT_OPTIONS = { + :Host => '0.0.0.0', + :Port => 8080, + :Threads => '0:16', + :Verbose => false +} + +def self.run(app, options = {}) + options = DEFAULT_OPTIONS.merge(options) + + if options[:Verbose] + app = Rack::CommonLogger.new(app, STDOUT) end + + if options[:environment] + ENV['RACK_ENV'] = options[:environment].to_s + end + + server = ::Puma::Server.new(app) + min, max = options[:Threads].split(':', 2) + + puts "Puma #{::Puma::Const::PUMA_VERSION} starting..." + puts "* Min threads: #{min}, max threads: #{max}" + puts "* Environment: #{ENV['RACK_ENV']}" + puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}" + + server.add_tcp_listener options[:Host], options[:Port] + server.min_threads = min + server.max_threads = max yield server if block_given? - server.run.join + + begin + server.run.join + rescue Interrupt + puts "* Gracefully stopping, waiting for requests to finish" + server.stop(true) + puts "* Goodbye!" + end + end ``` @@ -654,4 +703,4 @@ the last piece of our journey in the Rails initialization process. This high level overview will help you understand when your code is executed and how, and overall become a better Rails developer. If you still want to know more, the Rails source code itself is probably the -best place to go next. +best place to go next.
\ No newline at end of file diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index 93729c6f72..8f119f36aa 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -20,7 +20,7 @@ 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 +**Currently included series:** 4.1.z, 4.0.z Security Issues --------------- @@ -35,7 +35,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that security releases are easy to upgrade to if you're running the latest version of Rails. -**Currently included series:** 4.0.z, 3.2.z +**Currently included series:** 4.1.z, 4.0.z Severe Security Issues ---------------------- @@ -44,7 +44,7 @@ For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. -**Currently included series:** 4.0.z, 3.2.z +**Currently included series:** 4.1.z, 4.0.z, 3.2.z Unsupported Release Series -------------------------- diff --git a/guides/source/migrations.md b/guides/source/migrations.md index bfee55a95d..c61ccfe94a 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -495,6 +495,7 @@ class ExampleMigration < ActiveRecord::Migration add_column :users, :home_page_url, :string rename_column :users, :email, :email_address end +end ``` Using `reversible` will ensure that the instructions are executed in the diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 855fab18e3..4f0634d955 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -17,9 +17,9 @@ Model setup To be able to use the nested model functionality in your forms, the model will need to support some basic operations. -First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be build. +First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built. -If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded. +If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded. Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`. @@ -220,6 +220,6 @@ As you can see it has generated 2 `project name` inputs, one for each new `proje You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance. -NOTE: The reason that `fields_for` constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. +NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep. TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example. diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 9c92cf3aea..b1b4c8fa4e 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -27,10 +27,9 @@ Rails on Rack ### Rails Application's Rack Object -`ApplicationName::Application` is the primary Rack application object of a Rails +`Rails.application` is the primary Rack application object of a Rails application. Any Rack compliant web server should be using -`ApplicationName::Application` object to serve a Rails -application. `Rails.application` refers to the same application object. +`Rails.application` object to serve a Rails application. ### `rails server` @@ -141,7 +140,7 @@ use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag -run MyApp::Application.routes +run Rails.application.routes ``` The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below. @@ -201,7 +200,7 @@ use ActionDispatch::Static use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8> use Rack::Runtime ... -run Blog::Application.routes +run Rails.application.routes ``` If you want to remove session related middleware, do the following: diff --git a/guides/source/routing.md b/guides/source/routing.md index eef618f28d..0783bce442 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -694,6 +694,8 @@ namespace :admin do end ``` +NOTE: Request constraints work by calling a method on the <a href="action_controller_overview.html#the-request-object">Request object</a> with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String. + ### Advanced Constraints If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do: @@ -709,7 +711,7 @@ class BlacklistConstraint end end -TwitterClone::Application.routes.draw do +Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: BlacklistConstraint.new end @@ -718,7 +720,7 @@ end You can also specify constraints as a lambda: ```ruby -TwitterClone::Application.routes.draw do +Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) } end diff --git a/guides/source/security.md b/guides/source/security.md index a40c99cbfd..9d7fdb3c6d 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -151,7 +151,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec reset_session ``` -If you use the popular RestfulAuthentication plugin for user management, add reset\_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. +If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. @@ -239,24 +239,23 @@ Or the attacker places the code into the onmouseover event handler of an image: There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request. -To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller: +To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications: ```ruby -protect_from_forgery +protect_from_forgery with: :exception ``` -This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, the session will be reset. +This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown. It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself: ```ruby -def handle_unverified_request - super - sign_out_user # Example method that will destroy the user cookies. +rescue_from ActionController::InvalidAuthenticityToken do |exception| + sign_out_user # Example method that will destroy the user cookies end ``` -The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present on a non-GET request. +The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present or is incorrect on a non-GET request. Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later. @@ -314,7 +313,7 @@ def sanitize_filename(filename) end ``` -A significant disadvantage of synchronous processing of file uploads (as the attachment\_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server. +A significant disadvantage of synchronous processing of file uploads (as the attachment_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server. The solution to this is best to _process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background. @@ -1003,7 +1002,7 @@ _'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS at * X-Content-Type-Options _'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file. * X-Content-Security-Policy -[A powerful mechanism for controlling which sites certain content types can be loaded from](http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html) +[A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html) * Access-Control-Allow-Origin Used to control which sites are allowed to bypass same origin policies and send cross-origin requests. * Strict-Transport-Security diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index d58024df3d..da161f84c9 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -25,8 +25,6 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp 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!!!?" @@ -79,12 +77,15 @@ secrets, you need to: secret_key_base: production: - secret_key_base: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> ``` -2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to - `secrets.yml` under the `production` section. - +2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to + set the SECRET_KEY_BASE environment variable for whichever users run the Rails + app in production mode. Alternately, you can simply copy the existing + `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml` + under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'. + 3. Remove the `secret_token.rb` initializer. 4. Use `rake secret` to generate new keys for the `development` and `test` sections. @@ -104,9 +105,9 @@ Applications created before Rails 4.1 uses `Marshal` to serialize cookie values the signed and encrypted cookie jars. If you want to use the new `JSON`-based format in your application, you can add an initializer file with the following content: - ```ruby - Rails.application.config.cookies_serializer :hybrid - ``` +```ruby +Rails.application.config.action_dispatch.cookies_serializer = :hybrid +``` This would transparently migrate your existing `Marshal`-serialized cookies into the new `JSON`-based format. @@ -463,7 +464,7 @@ being used, you can update your form to use the `PUT` method instead: <%= form_for [ :update_name, @user ], method: :put do |f| %> ``` -For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/) +For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/) on the Rails blog. #### A note about media types diff --git a/rails.gemspec b/rails.gemspec index d1c199a97a..4800df0df4 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -27,5 +27,5 @@ Gem::Specification.new do |s| s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0' + s.add_dependency 'sprockets-rails', '~> 2.1' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index afbebf5b95..6a31a923a7 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,13 @@ +* Fix `console` and `generators` blocks defined at different environments. + + Fixes #14748. + + *Rafael Mendonça França* + +* Move configuration of asset precompile list and version to an initializer. + + *Matthew Draper* + * Do not set the Rails environment to test by default when using test_unit Railtie. *Konstantin Shabanov* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index e37347b576..2fde974732 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -87,7 +87,7 @@ module Rails class << self def inherited(base) super - Rails.application ||= base.instance + base.instance end # Makes the +new+ method public. @@ -117,6 +117,8 @@ module Rails @railties = nil @message_verifiers = {} + Rails.application ||= self + add_lib_to_load_path! ActiveSupport.run_load_hooks(:before_configuration, self) @@ -151,14 +153,13 @@ module Rails def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 - @caching_key_generator ||= begin + @caching_key_generator ||= if secrets.secret_key_base key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) ActiveSupport::CachingKeyGenerator.new(key_generator) else ActiveSupport::LegacyKeyGenerator.new(config.secret_token) end - end end # Returns a message verifier object. @@ -230,6 +231,18 @@ module Rails self.class.runner(&blk) end + # Sends any console called in the instance of a new application up + # to the +console+ method defined in Rails::Railtie. + def console(&blk) + self.class.console(&blk) + end + + # Sends any generators called in the instance of a new application up + # to the +generators+ method defined in Rails::Railtie. + def generators(&blk) + self.class.generators(&blk) + end + # Sends the +isolate_namespace+ method up to the class method. def isolate_namespace(mod) self.class.isolate_namespace(mod) @@ -332,6 +345,25 @@ module Rails config.helpers_paths end + console do + require "pp" + end + + console do + unless ::Kernel.private_method_defined?(:y) + if RUBY_VERSION >= '2.0' + require "psych/y" + else + module ::Kernel + def y(*objects) + puts ::Psych.dump_stream(*objects) + end + private :y + end + end + end + end + protected alias :build_middleware_stack :app diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 9aec2f9734..4c449d2c57 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -95,6 +95,7 @@ module Rails yaml = Pathname.new(paths["config/database"].first || "") config = if yaml.exist? + require "yaml" require "erb" YAML.load(ERB.new(yaml.read).result) || {} elsif ENV['DATABASE_URL'] diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index f6bdf129d6..555d8f31e1 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -18,7 +18,14 @@ module Rails opt.on("-e", "--environment=name", String, "Specifies the environment to run this console under (test/development/production).", "Default: development") { |v| options[:environment] = v.strip } - opt.on("--debugger", 'Enable the debugger.') { |v| options[:debugger] = v } + opt.on("--debugger", 'Enable the debugger.') do |v| + if RUBY_VERSION < '2.0.0' + options[:debugger] = v + else + puts "=> Notice: debugger option is ignored since ruby 2.0 and " \ + "it will be removed in future versions" + end + end opt.parse!(arguments) end @@ -69,12 +76,25 @@ module Rails Rails.env = environment end - def debugger? - options[:debugger] + if RUBY_VERSION < '2.0.0' + def debugger? + options[:debugger] + end + + def require_debugger + require 'debugger' + puts "=> Debugger enabled" + rescue LoadError + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." + exit(1) + end end def start - require_debugger if debugger? + if RUBY_VERSION < '2.0.0' + require_debugger if debugger? + end + set_environment! if environment? if sandbox? @@ -89,13 +109,5 @@ module Rails end console.start end - - def require_debugger - require 'debugger' - puts "=> Debugger enabled" - rescue LoadError - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." - exit(1) - end end end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index fec4962fb5..6146b6c1db 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -18,7 +18,14 @@ module Rails opts.on("-c", "--config=file", String, "Use custom rackup configuration file") { |v| options[:config] = v } opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true } - opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true } + opts.on("-u", "--debugger", "Enable the debugger") do + if RUBY_VERSION < '2.0.0' + options[:debugger] = true + else + puts "=> Notice: debugger option is ignored since ruby 2.0 and " \ + "it will be removed in future versions" + end + end opts.on("-e", "--environment=name", String, "Specifies the environment to run this server under (test/development/production).", "Default: development") { |v| options[:environment] = v } @@ -75,7 +82,9 @@ module Rails def middleware middlewares = [] - middlewares << [Rails::Rack::Debugger] if options[:debugger] + if RUBY_VERSION < '2.0.0' + middlewares << [Rails::Rack::Debugger] if options[:debugger] + end middlewares << [::Rack::ContentLength] # FIXME: add Rack::Lock in the case people are using webrick. diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 5661094d95..b36ab3d0d5 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -429,7 +429,6 @@ module Rails # Load console and invoke the registered hooks. # Check <tt>Rails::Railtie.console</tt> for more info. def load_console(app=self) - require "pp" require "rails/console/app" require "rails/console/helpers" run_console_blocks(app) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 2fb49479fe..c066f748ee 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -249,7 +249,7 @@ module Rails 'Use SCSS for stylesheets') else gems << GemfileEntry.version('sass-rails', - '~> 4.0.2', + '~> 4.0.3', 'Use SCSS for stylesheets') end diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index 69c10efa47..10f80abb15 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -4,8 +4,8 @@ <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> <ul> - <%% @<%= singular_table_name %>.errors.full_messages.each do |msg| %> - <li><%%= msg %></li> + <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %> + <li><%%= message %></li> <%% end %> </ul> </div> diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index abf6909a7f..8675d8bc1e 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -231,6 +231,12 @@ module Rails end end + def delete_assets_initializer_skipping_sprockets + if options[:skip_sprockets] + remove_file 'config/initializers/assets.rb' + end + end + def finish_template build(:leftovers) end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index a9b6787894..448b6f4845 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -23,11 +23,15 @@ source 'https://rubygems.org' # gem 'capistrano-rails', group: :development <% unless defined?(JRUBY_VERSION) -%> -# Use debugger +# To use a debugger + <%- if RUBY_VERSION < '2.0.0' -%> # gem 'debugger', group: [:development, :test] + <%- else -%> +# gem 'byebug', group: [:development, :test] + <%- end -%> <% end -%> <% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince/) -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin] +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml index 138e3a8664..34fc0e3465 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml @@ -23,7 +23,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="frontbase://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml index 2cdb592eeb..d088dd62bf 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml @@ -61,7 +61,27 @@ test: <<: *default database: <%= app_name[0,4] %>_tst -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="ibm-db://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml index cefd30d519..db0a429753 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -53,7 +53,16 @@ test: <<: *default url: jdbc:db://localhost/<%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# production: - url: <%%= ENV["DATABASE_URL"] %> + url: jdbc:db://localhost/<%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index d31761349c..acb93939e1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -26,6 +26,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. -production: <%%= ENV["DATABASE_URL"] %> +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 0d248dc197..9e99264d33 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -42,7 +42,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 66eba3bf0d..28c36eb82f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -18,7 +18,6 @@ test: <<: *default database: db/test.sqlite3 -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml index d618fc28a6..4b2e6646c7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -32,11 +32,27 @@ test: <<: *default database: <%= app_name %>_test -# Avoid production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. # -# Example: -# mysql2://myuser:mypass@localhost/somedatabase +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> # production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml index d469ec0f99..10ab4c02e2 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml @@ -32,7 +32,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index 93f48656b2..feb25bbc6b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -59,11 +59,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. # -# Example: -# postgres://myuser:mypass@localhost/somedatabase +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> # production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml index 7312ddb6cd..1c1a37ca8d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml @@ -20,11 +20,6 @@ test: <<: *default database: db/test.sqlite3 -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. -# -# Example: -# sqlite3://myuser:mypass@localhost/full/path/to/somedatabase -# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml index aa960e493e..30b0df34a8 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -42,7 +42,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="sqlserver://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index b789ed9a94..9ed71687ea 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -33,12 +33,7 @@ Rails.application.configure do # Generate digests for assets URLs. config.assets.digest = true - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) + # `config.assets.precompile` has moved to config/initializers/assets.rb <%- end -%> # Specifies the header that your server uses for sending files. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt new file mode 100644 index 0000000000..d2f4ec33a6 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb index 7a06a89f0f..7f70458dee 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.action_dispatch.cookies_serializer = :json
\ No newline at end of file +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb index 72aca7e441..dc1899682b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb @@ -2,4 +2,3 @@ # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf -# Mime::Type.register_alias "text/html", :iphone diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index f0a832f783..1f704db510 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -38,6 +38,10 @@ end <% end -%> <% unless defined?(JRUBY_VERSION) -%> -# To use debugger -# gem 'debugger' +# To use a debugger + <%- if RUBY_VERSION < '2.0.0' -%> +# gem 'debugger', group: [:development, :test] + <%- else -%> +# gem 'byebug', group: [:development, :test] + <%- end -%> <% end -%> diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 0e69aa101f..2c3b04043f 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -31,7 +31,7 @@ class <%= controller_class_name %>Controller < ApplicationController if @<%= orm_instance.save %> redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> else - render action: 'new' + render :new end end @@ -40,7 +40,7 @@ class <%= controller_class_name %>Controller < ApplicationController if @<%= orm_instance.update("#{singular_table_name}_params") %> redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> else - render action: 'edit' + render :edit end end diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index d1ee96f7fd..886f0e52e1 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -1,6 +1,6 @@ module Rails module Rack - autoload :Debugger, "rails/rack/debugger" + autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0' autoload :Logger, "rails/rack/logger" autoload :LogTailer, "rails/rack/log_tailer" end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 8d7e804bdc..2b33beaa2b 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -221,26 +221,28 @@ module Rails protected def run_console_blocks(app) #:nodoc: - self.class.console.each { |block| block.call(app) } + each_registered_block(:console) { |block| block.call(app) } end def run_generators_blocks(app) #:nodoc: - self.class.generators.each { |block| block.call(app) } + each_registered_block(:generators) { |block| block.call(app) } end def run_runner_blocks(app) #:nodoc: - self.class.runner.each { |block| block.call(app) } + each_registered_block(:runner) { |block| block.call(app) } end def run_tasks_blocks(app) #:nodoc: extend Rake::DSL - self.class.rake_tasks.each { |block| instance_exec(app, &block) } + each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) } + end - # Load also tasks from all superclasses - klass = self.class.superclass + private - while klass.respond_to?(:rake_tasks) - klass.rake_tasks.each { |t| instance_exec(app, &t) } + def each_registered_block(type, &block) + klass = self.class + while klass.respond_to?(type) + klass.public_send(type).each(&block) klass = klass.superclass end end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 2e6356343d..c837fadb40 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -4,6 +4,7 @@ abort("Abort testing: Your Rails environment is running in production mode!") if require 'active_support/testing/autorun' require 'active_support/test_case' +require 'action_controller' require 'action_controller/test_case' require 'action_dispatch/testing/integration' require 'rails/generators/test_case' diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index b235b51d90..51f55a560f 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -199,6 +199,7 @@ module ApplicationTests end test "precompile creates a manifest file with all the assets listed" do + app_file "app/assets/images/rails.png", "notactuallyapng" app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" app_file "app/assets/javascripts/application.js", "alert();" # digest is default in false, we must enable it for test environment @@ -260,7 +261,7 @@ module ApplicationTests test "precompile shouldn't use the digests present in manifest.json" do app_file "app/assets/images/rails.png", "notactuallyapng" - app_file "app/assets/stylesheets/application.css.erb", "//= depend_on rails.png\np { url: <%= asset_path('rails.png') %> }" + app_file "app/assets/stylesheets/application.css.erb", "p { url: <%= asset_path('rails.png') %> }" ENV["RAILS_ENV"] = "production" precompile! @@ -448,23 +449,23 @@ module ApplicationTests test "asset urls should be protocol-relative if no request is in scope" do app_file "app/assets/images/rails.png", "notreallyapng" - app_file "app/assets/javascripts/image_loader.js.erb", 'var src="<%= image_path("rails.png") %>";' + app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';" add_to_config "config.assets.precompile = %w{image_loader.js}" add_to_config "config.asset_host = 'example.com'" precompile! - assert_match 'src="//example.com/assets/rails.png"', File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) + assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) end test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri" app_file "app/assets/images/rails.png", "notreallyapng" - app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";' + app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';" add_to_config "config.assets.precompile = %w{app.js}" precompile! - assert_match 'src="/sub/uri/assets/rails.png"', File.read(Dir["#{app_path}/public/assets/app-*.js"].first) + assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first) end test "assets:cache:clean should clean cache" do diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index b11fd55170..09aba1c2e9 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -489,7 +489,7 @@ module ApplicationTests test "valid timezone is setup correctly" do add_to_config <<-RUBY config.root = "#{app_path}" - config.time_zone = "Wellington" + config.time_zone = "Wellington" RUBY require "#{app_path}/config/environment" @@ -500,7 +500,7 @@ module ApplicationTests test "raises when an invalid timezone is defined in the config" do add_to_config <<-RUBY config.root = "#{app_path}" - config.time_zone = "That big hill over yonder hill" + config.time_zone = "That big hill over yonder hill" RUBY assert_raise(ArgumentError) do @@ -511,7 +511,7 @@ module ApplicationTests test "valid beginning of week is setup correctly" do add_to_config <<-RUBY config.root = "#{app_path}" - config.beginning_of_week = :wednesday + config.beginning_of_week = :wednesday RUBY require "#{app_path}/config/environment" @@ -522,7 +522,7 @@ module ApplicationTests test "raises when an invalid beginning of week is defined in the config" do add_to_config <<-RUBY config.root = "#{app_path}" - config.beginning_of_week = :invalid + config.beginning_of_week = :invalid RUBY assert_raise(ArgumentError) do @@ -803,5 +803,81 @@ module ApplicationTests assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/] end + + test "rake_tasks block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + rake_tasks do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + + Rails.application.load_tasks + assert $ran_block + end + + test "generators block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + generators do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_generators + assert $ran_block + end + + test "console block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + console do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_console + assert $ran_block + end + + test "runner block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + runner do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_runner + assert $ran_block + end end end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 3601a58f67..8e76bf27f3 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -216,8 +216,8 @@ module ApplicationTests require "#{app_path}/config/environment" orig_database_url = ENV.delete("DATABASE_URL") orig_rails_env, Rails.env = Rails.env, 'development' - database_url_db_name = File.join(app_path, "db/database_url_db.sqlite3") - ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}" + database_url_db_name = "db/database_url_db.sqlite3" + ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb index 5bfea599e0..f8d8a673ae 100644 --- a/railties/test/application/multiple_applications_test.rb +++ b/railties/test/application/multiple_applications_test.rb @@ -21,6 +21,12 @@ module ApplicationTests assert_equal Rails.application.config.secret_key_base, clone.config.secret_key_base, "The base secret key on the config should be the same" end + def test_inheriting_multiple_times_from_application + new_application_class = Class.new(Rails::Application) + + assert_not_equal Rails.application.object_id, new_application_class.instance.object_id + end + def test_initialization_of_multiple_copies_of_same_application application1 = AppTemplate::Application.new application2 = AppTemplate::Application.new @@ -116,6 +122,26 @@ module ApplicationTests assert_equal 3, $run_count, "There should have been three initializers that incremented the count" end + def test_consoles_run_on_different_applications_go_to_the_same_class + $run_count = 0 + AppTemplate::Application.console { $run_count += 1 } + AppTemplate::Application.new.console { $run_count += 1 } + + assert_equal 0, $run_count, "Without loading the consoles, the count should be 0" + Rails.application.load_console + assert_equal 2, $run_count, "There should have been two consoles that increment the count" + end + + def test_generators_run_on_different_applications_go_to_the_same_class + $run_count = 0 + AppTemplate::Application.generators { $run_count += 1 } + AppTemplate::Application.new.generators { $run_count += 1 } + + assert_equal 0, $run_count, "Without loading the generators, the count should be 0" + Rails.application.load_generators + assert_equal 2, $run_count, "There should have been two generators that increment the count" + end + def test_runners_run_on_different_applications_go_to_the_same_class $run_count = 0 AppTemplate::Application.runner { $run_count += 1 } diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index b2c52a092f..15414db00f 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -17,11 +17,11 @@ module ApplicationTests end def database_url_db_name - File.join(app_path, "db/database_url_db.sqlite3") + "db/database_url_db.sqlite3" end def set_database_url - ENV['DATABASE_URL'] = File.join("sqlite3://:@localhost", database_url_db_name) + ENV['DATABASE_URL'] = "sqlite3:#{database_url_db_name}" # ensure it's using the DATABASE_URL FileUtils.rm_rf("#{app_path}/config/database.yml") end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index a34beaedb3..1273f9d4c2 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -19,14 +19,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert console.sandbox? end - def test_debugger_option - console = Rails::Console.new(app, parse_arguments(["--debugger"])) - assert console.debugger? - end - def test_no_options console = Rails::Console.new(app, parse_arguments([])) - assert !console.debugger? assert !console.sandbox? end @@ -36,13 +30,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/Loading \w+ environment \(Rails/, output) end - def test_start_with_debugger - rails_console = Rails::Console.new(app, parse_arguments(["--debugger"])) - rails_console.expects(:require_debugger).returns(nil) - - silence_stream(STDOUT) { rails_console.start } - end - def test_start_with_sandbox app.expects(:sandbox=).with(true) FakeConsole.expects(:start) @@ -52,6 +39,25 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/Loading \w+ environment in sandbox \(Rails/, output) end + if RUBY_VERSION < '2.0.0' + def test_debugger_option + console = Rails::Console.new(app, parse_arguments(["--debugger"])) + assert console.debugger? + end + + def test_no_options_does_not_set_debugger_flag + console = Rails::Console.new(app, parse_arguments([])) + assert !console.debugger? + end + + def test_start_with_debugger + rails_console = Rails::Console.new(app, parse_arguments(["--debugger"])) + rails_console.expects(:require_debugger).returns(nil) + + silence_stream(STDOUT) { rails_console.start } + end + end + def test_console_with_environment start ["-e production"] assert_match(/\sproduction\s/, output) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 8e1aeddb2b..007dd886da 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -237,6 +237,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] + assert_no_file "config/initializers/assets.rb" assert_file "config/application.rb" do |content| assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) end @@ -252,7 +253,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/config\.assets\.digest = true/, content) assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) assert_no_match(/config\.assets\.css_compressor = :sass/, content) - assert_no_match(/config\.assets\.version = '1\.0'/, content) end end @@ -305,14 +305,17 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /gem 'jbuilder'/ end - def test_inclusion_of_debugger + def test_inclusion_of_a_debugger run_generator if defined?(JRUBY_VERSION) assert_file "Gemfile" do |content| + assert_no_match(/byebug/, content) assert_no_match(/debugger/, content) end - else + elsif RUBY_VERSION < '2.0.0' assert_file "Gemfile", /# gem 'debugger'/ + else + assert_file "Gemfile", /# gem 'byebug'/ end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 7a2701f813..853af80111 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -64,14 +64,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ end - def test_inclusion_of_debugger + def test_inclusion_of_a_debugger run_generator [destination_root, '--full'] if defined?(JRUBY_VERSION) assert_file "Gemfile" do |content| + assert_no_match(/byebug/, content) assert_no_match(/debugger/, content) end - else + elsif RUBY_VERSION < '2.0.0' assert_file "Gemfile", /# gem 'debugger'/ + else + assert_file "Gemfile", /# gem 'byebug'/ end end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 26e56a162c..3c1123b53d 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -160,13 +160,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase Unknown::Generators.send :remove_const, :ActiveModel end - def test_new_hash_style - run_generator - assert_file "app/controllers/users_controller.rb" do |content| - assert_match(/render action: 'new'/, content) - end - end - def test_model_name_option run_generator ["Admin::User", "--model-name=User"] assert_file "app/controllers/admin/users_controller.rb" do |content| diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 4cbd4822be..a458240d2f 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -72,6 +72,14 @@ module RailtiesTest assert $to_prepare end + test "railtie have access to application in before_configuration callbacks" do + $after_initialize = false + class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = Rails.root.to_path } ; end + assert_not $before_configuration + require "#{app_path}/config/environment" + assert_equal app_path, $before_configuration + end + test "railtie can add after_initialize callbacks" do $after_initialize = false class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end diff --git a/tasks/release.rb b/tasks/release.rb index 849117884c..767feaf236 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -67,7 +67,7 @@ end namespace :changelog do task :release_date do - FRAMEWORKS + ['guides'].each do |fw| + (FRAMEWORKS + ['guides']).each do |fw| require 'date' replace = '\1(' + Date.today.strftime('%B %d, %Y') + ')' fname = File.join fw, 'CHANGELOG.md' @@ -78,7 +78,7 @@ namespace :changelog do end task :release_summary do - FRAMEWORKS + ['guides'].each do |fw| + (FRAMEWORKS + ['guides']).each do |fw| puts "## #{fw}" fname = File.join fw, 'CHANGELOG.md' contents = File.readlines fname |