diff options
127 files changed, 672 insertions, 446 deletions
@@ -15,6 +15,7 @@ gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master' gem 'coffee-rails', '~> 4.1.0' gem 'turbolinks' gem 'arel', github: 'rails/arel', branch: 'master' +gem 'mail', github: 'mikel/mail' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) diff --git a/Gemfile.lock b/Gemfile.lock index d2ed998160..e34449a7b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,6 +12,13 @@ GIT redis-namespace GIT + remote: git://github.com/mikel/mail.git + revision: b159e0a542962fdd5e292a48cfffa560d7cf412e + specs: + mail (2.6.3.edge) + mime-types (>= 1.16, < 3) + +GIT remote: git://github.com/rails/arel.git revision: aac9da257f291ad8d2d4f914528881c240848bb2 branch: master @@ -41,7 +48,7 @@ PATH actionview (= 5.0.0.alpha) activesupport (= 5.0.0.alpha) rack (~> 1.6) - rack-test (~> 0.6.2) + rack-test (~> 0.6.3) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.1) actionview (5.0.0.alpha) @@ -123,8 +130,6 @@ GEM nokogiri loofah (2.0.1) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) metaclass (0.0.4) mime-types (2.4.3) mini_portile (0.6.2) @@ -250,6 +255,7 @@ DEPENDENCIES jquery-rails! json kindlerb (= 0.1.1) + mail! minitest (< 5.3.4) mocha (~> 0.14) mysql (>= 2.9.0) diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb index 227192f48d..239974e7b1 100644 --- a/actionmailer/lib/action_mailer/mail_helper.rb +++ b/actionmailer/lib/action_mailer/mail_helper.rb @@ -4,7 +4,17 @@ module ActionMailer # attachments list. module MailHelper # Take the text and format it, indented two spaces for each line, and - # wrapped at 72 columns. + # wrapped at 72 columns: + # + # text = <<-TEXT + # This is + # the paragraph. + # + # * item1 * item2 + # TEXT + # + # block_format text + # # => " This is the paragraph.\n\n * item1\n * item2\n" def block_format(text) formatted = text.split(/\n\r?\n/).collect { |paragraph| format_paragraph(paragraph) diff --git a/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb b/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb deleted file mode 100644 index a2187308b6..0000000000 --- a/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb +++ /dev/null @@ -1 +0,0 @@ -body_text
\ No newline at end of file diff --git a/actionmailer/test/fixtures/first_mailer/share.erb b/actionmailer/test/fixtures/first_mailer/share.erb deleted file mode 100644 index da43638ceb..0000000000 --- a/actionmailer/test/fixtures/first_mailer/share.erb +++ /dev/null @@ -1 +0,0 @@ -first mail diff --git a/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb b/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb deleted file mode 100644 index 2d0cd5c124..0000000000 --- a/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb +++ /dev/null @@ -1 +0,0 @@ -Have some dots. Enjoy!
\ No newline at end of file diff --git a/actionmailer/test/fixtures/second_mailer/share.erb b/actionmailer/test/fixtures/second_mailer/share.erb deleted file mode 100644 index 9a54010672..0000000000 --- a/actionmailer/test/fixtures/second_mailer/share.erb +++ /dev/null @@ -1 +0,0 @@ -second mail diff --git a/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb b/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb deleted file mode 100644 index 3b4ba35f20..0000000000 --- a/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb +++ /dev/null @@ -1 +0,0 @@ -let's go!
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml b/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml deleted file mode 100644 index 8dcf9746cc..0000000000 --- a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -%p Hello there, - -%p - Mr. - = @recipient - from haml
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml b/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml deleted file mode 100644 index 8dcf9746cc..0000000000 --- a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml +++ /dev/null @@ -1,6 +0,0 @@ -%p Hello there, - -%p - Mr. - = @recipient - from haml
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb deleted file mode 100644 index 946d99ede5..0000000000 --- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -<html> - <body> - HTML formatted message to <strong><%= @recipient %></strong>. - </body> -</html> -<html> - <body> - HTML formatted message to <strong><%= @recipient %></strong>. - </body> -</html> diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ deleted file mode 100644 index 946d99ede5..0000000000 --- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ +++ /dev/null @@ -1,10 +0,0 @@ -<html> - <body> - HTML formatted message to <strong><%= @recipient %></strong>. - </body> -</html> -<html> - <body> - HTML formatted message to <strong><%= @recipient %></strong>. - </body> -</html> diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb deleted file mode 100644 index 6940419d47..0000000000 --- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb +++ /dev/null @@ -1 +0,0 @@ -Ignored when searching for implicitly multipart parts. diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak deleted file mode 100644 index 6940419d47..0000000000 --- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak +++ /dev/null @@ -1 +0,0 @@ -Ignored when searching for implicitly multipart parts. diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb deleted file mode 100644 index a6c8d54cf9..0000000000 --- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb +++ /dev/null @@ -1,2 +0,0 @@ -Plain text to <%= @recipient %>. -Plain text to <%= @recipient %>. diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb deleted file mode 100644 index c14348c770..0000000000 --- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb +++ /dev/null @@ -1 +0,0 @@ -yaml to: <%= @recipient %>
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb b/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb deleted file mode 100644 index ae3cfa77e7..0000000000 --- a/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb +++ /dev/null @@ -1 +0,0 @@ -Hey Ho, <%= render partial: "subtemplate" %>
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb b/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb deleted file mode 100644 index 73ea14f82f..0000000000 --- a/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb +++ /dev/null @@ -1 +0,0 @@ -<strong>foo</strong> <%= @foo %>
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb b/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb deleted file mode 100644 index 779fe4c1ea..0000000000 --- a/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb +++ /dev/null @@ -1 +0,0 @@ -foo: <%= @foo %>
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/rxml_template.rxml b/actionmailer/test/fixtures/test_mailer/rxml_template.rxml deleted file mode 100644 index d566bd8d7c..0000000000 --- a/actionmailer/test/fixtures/test_mailer/rxml_template.rxml +++ /dev/null @@ -1,2 +0,0 @@ -xml.instruct! -xml.test
\ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/signed_up.html.erb b/actionmailer/test/fixtures/test_mailer/signed_up.html.erb deleted file mode 100644 index 7afe1f651c..0000000000 --- a/actionmailer/test/fixtures/test_mailer/signed_up.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -Hello there, - -Mr. <%= @recipient %>
\ No newline at end of file diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a569e0eb6b..8c4ba2a154 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -21,6 +21,14 @@ *David Ilizarov* +* Change filter on /rails/info/routes to use an actual path regexp from rails + and not approximate javascript version. Oniguruma supports much more + extensive list of features than javascript regexp engine. + + Fixes #18402. + + *Ravil Bayramgalin* + * Non-string authenticity tokens do not raise NoMethodError when decoding the masked token. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index d9b23ad4a9..d907001bd6 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'rack', '~> 1.6' - s.add_dependency 'rack-test', '~> 0.6.2' + s.add_dependency 'rack-test', '~> 0.6.3' s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1' s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' s.add_dependency 'actionview', version diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index f4fd1db36c..59ffb0a19e 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation' - module AbstractController module Callbacks extend ActiveSupport::Concern @@ -70,7 +68,7 @@ module AbstractController end def skip_filter(*names) - ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.") + ActiveSupport::Deprecation.warn("`skip_filter` is deprecated and will be removed in Rails 5.1. Use skip_before_action, skip_after_action or skip_around_action instead.") skip_action_callback(*names) end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index a9c3e438fb..819837e767 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -93,6 +93,10 @@ module ActionController super(args) end + # Return a list of helper names in specific path. + # + # ActionController::Base.all_helpers_from_path 'app/helpers' + # # => ["application", "chart", "rubygems"] def all_helpers_from_path(path) helpers = Array(path).flat_map do |_path| extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 01bbd749c1..f19c4201ba 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' -require 'active_support/deprecation' require 'active_support/rescuable' require 'action_dispatch/http/upload' require 'stringio' diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 07b3814ca4..732ee67268 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -114,7 +114,7 @@ module ActionDispatch end def engine_script_name(_routes) # :nodoc: - env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"] + env[_routes.env_key] end def request_method=(request_method) #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 001b14ec97..7da6301ac4 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -67,7 +67,7 @@ module ActionDispatch end def path_for(options) - path = options[:script_name].to_s.chomp("/") + path = options[:script_name].to_s.chomp("/".freeze) path << options[:path] if options.key?(:path) add_trailing_slash(path) if options[:trailing_slash] diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index ed63a5bd8b..cc4bd6105d 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -121,7 +121,6 @@ module ActionDispatch end def match_head_routes(routes, req) - routes.delete_if { |route| route.verb == // } head_routes = match_routes(routes, req) if head_routes.empty? diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb index 625050dc4b..857e49a682 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -2,12 +2,15 @@ require 'action_dispatch/middleware/session/abstract_store' module ActionDispatch module Session - # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful + # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful # if you don't store critical data in your sessions and you don't need them to live for extended periods # of time. + # + # ==== Options + # * <tt>cache</tt> - The cache to use. If it is not specified, <tt>Rails.cache</tt> will be used. + # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring. + # By default, the <tt>:expires_in</tt> option of the cache is used. class CacheStore < AbstractStore - # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is - # not specified, <tt>Rails.cache</tt> will be used. def initialize(app, options = {}) @cache = options[:cache] || Rails.cache options[:expire_after] ||= @cache.options[:expires_in] diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index ed25c67ae5..d8f9614904 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -52,6 +52,16 @@ module ActionDispatch # JavaScript before upgrading. # # Note that changing the secret key will invalidate all existing sessions! + # + # Because CookieStore extends Rack::Session::Abstract::ID, many of the + # options described there can be used to customize the session cookie that + # is generated. For example: + # + # Rails.application.config.session_store :cookie_store, expire_after: 14.days + # + # would set the session cookie to expire automatically 14 days after creation. + # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and + # <tt>:httponly</tt>. class CookieStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index b4d6629c35..cb19786f0b 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -8,6 +8,10 @@ end module ActionDispatch module Session + # A session store that uses MemCache to implement storage. + # + # ==== Options + # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring. class MemCacheStore < Rack::Session::Dalli include Compatibility include StaleSessionCheck diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb index 24e44f31ac..6e995c85c1 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb @@ -4,13 +4,13 @@ <%= route[:name] %><span class='helper'>_path</span> <% end %> </td> - <td data-route-verb='<%= route[:verb] %>'> + <td> <%= route[:verb] %> </td> - <td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'> + <td data-route-path='<%= route[:path] %>'> <%= route[:path] %> </td> - <td data-route-reqs='<%= route[:reqs] %>'> - <%= route[:reqs] %> + <td> + <%=simple_format route[:reqs] %> </td> </tr> 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 5cee0b5932..429ea7057c 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -81,92 +81,87 @@ </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++) { - func(elems[i]); - } - } - - // Sets innerHTML for an element - function setContent(elem, text) { - elem.innerHTML = text; - } + // support forEarch iterator on NodeList + NodeList.prototype.forEach = Array.prototype.forEach; // 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"); - - showMatch(string, regexp, section, elem); + // Check if there are any matched results in a section + function checkNoMatch(section, noMatchText) { + if (section.children.length <= 1) { + section.innerHTML += noMatchText; + } } - // 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; - - showMatch(string, regexp, section, elem); + // get JSON from url and invoke callback with result + function getJSON(url, success) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.onload = function() { + if (this.status == 200) + success(JSON.parse(this.response)); + }; + xhr.send(); } - // 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)); + function delayedKeyup(input, callback) { + var timeout; + input.onkeyup = function(){ + if (timeout) clearTimeout(timeout); + timeout = setTimeout(callback, 300); } } - // 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 + // remove params or fragments function sanitizePath(path) { - var path = path.charAt(0) == '/' ? path : "/" + path; - return path.replace(/\#.*|\?.*/, ''); + return path.replace(/[#?].*/, ''); } - var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), - searchElem = document.querySelector('#search'), - exactMatches = document.querySelector('#exact_matches'), - fuzzyMatches = document.querySelector('#fuzzy_matches'); + var pathElements = document.querySelectorAll('#route_table [data-route-path]'), + searchElem = document.querySelector('#search'), + exactSection = document.querySelector('#exact_matches'), + fuzzySection = document.querySelector('#fuzzy_matches'); // Remove matches when no search value is present searchElem.onblur = function(e) { if (searchElem.value === "") { - setContent(exactMatches, ""); - setContent(fuzzyMatches, ""); + exactSection.innerHTML = ""; + fuzzySection.innerHTML = ""; } } // On key press perform a search for matching paths - searchElem.onkeyup = function(e){ - var userInput = searchElem.value, - defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>', - defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>', + delayedKeyup(searchElem, function() { + var path = sanitizePath(searchElem.value), + defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + path +'):</th></tr>', + defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + path +'):</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 - setContent(exactMatches, defaultExactMatch); - setContent(fuzzyMatches, defaultFuzzyMatch); + if (!path) + return searchElem.onblur(); - // Display exact matches and fuzzy matches - each(regexpElems, function(elem) { - checkExactMatch(exactMatches, elem, userInput); - checkFuzzyMatch(fuzzyMatches, elem, userInput); - }) + getJSON('/rails/info/routes?path=' + path, function(matches){ + // Clear out results section + exactSection.innerHTML = defaultExactMatch; + fuzzySection.innerHTML = defaultFuzzyMatch; - // Display 'No Matches' message when no matches are found - checkNoMatch(exactMatches, defaultExactMatch, noExactMatch); - checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch); - } + // Display exact matches and fuzzy matches + pathElements.forEach(function(elem) { + var elemPath = elem.getAttribute('data-route-path'); + + if (matches['exact'].indexOf(elemPath) != -1) + exactSection.appendChild(elem.parentNode.cloneNode(true)); + + if (matches['fuzzy'].indexOf(elemPath) != -1) + fuzzySection.appendChild(elem.parentNode.cloneNode(true)); + }) + + // Display 'No Matches' message when no matches are found + checkNoMatch(exactSection, noExactMatch); + checkNoMatch(fuzzySection, noFuzzyMatch); + }) + }) } // Enables functionality to toggle between `_path` and `_url` helper suffixes @@ -174,19 +169,20 @@ // Sets content for each element function setValOn(elems, val) { - each(elems, function(elem) { - setContent(elem, val); + elems.forEach(function(elem) { + elem.innerHTML = val; }); } // Sets onClick event for each element function onClick(elems, func) { - each(elems, function(elem) { + elems.forEach(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'); diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index df5ebb6751..c513737fc2 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -28,23 +28,6 @@ module ActionDispatch super.to_s end - def regexp - __getobj__.path.to_regexp - end - - def json_regexp - str = regexp.inspect. - sub('\\A' , '^'). - sub('\\Z' , '$'). - sub('\\z' , '$'). - sub(/^\// , ''). - sub(/\/[a-z]*$/ , ''). - gsub(/\(\?#.+\)/ , ''). - gsub(/\(\?-\w+:/ , '('). - gsub(/\s/ , '') - Regexp.new(str).source - end - def reqs @reqs ||= begin reqs = endpoint @@ -117,11 +100,10 @@ module ActionDispatch end.reject(&:internal?).collect do |route| collect_engine_routes(route) - { name: route.name, - verb: route.verb, - path: route.path, - reqs: route.reqs, - regexp: route.json_regexp } + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs } end end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 2e116ea9cd..9934f5547a 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -247,14 +247,12 @@ module ActionDispatch args = [] model = record.to_model - name = if model.persisted? - args << model - model.model_name.singular_route_key - else - @key_strategy.call model.model_name - end - - named_route = prefix + "#{name}_#{suffix}" + named_route = if model.persisted? + args << model + get_method_for_string model.model_name.singular_route_key + else + get_method_for_class model + end [named_route, args] end @@ -309,11 +307,11 @@ module ActionDispatch def get_method_for_class(klass) name = @key_strategy.call klass.model_name - prefix + "#{name}_#{suffix}" + get_method_for_string name end def get_method_for_string(str) - prefix + "#{str}_#{suffix}" + "#{prefix}#{str}_#{suffix}" end [nil, 'new', 'edit'].each do |action| diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ce04f0b08a..58b3fff9d7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -199,12 +199,9 @@ module ActionDispatch private def optimized_helper(args) - params = parameterize_args(args) - missing_keys = missing_keys(params) - - unless missing_keys.empty? - raise_generation_error(params, missing_keys) - end + params = parameterize_args(args) { |k| + raise_generation_error(args) + } @route.format params end @@ -215,16 +212,21 @@ module ActionDispatch def parameterize_args(args) params = {} - @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v } + @arg_size.times { |i| + key = @required_parts[i] + value = args[i].to_param + yield key if value.nil? || value.empty? + params[key] = value + } params end - def missing_keys(args) - args.select{ |part, arg| arg.nil? || arg.empty? }.keys - end - - def raise_generation_error(args, missing_keys) - constraints = Hash[@route.requirements.merge(args).sort] + def raise_generation_error(args) + missing_keys = [] + params = parameterize_args(args) { |missing_key| + missing_keys << missing_key + } + constraints = Hash[@route.requirements.merge(params).sort] message = "No route matches #{constraints.inspect}" message << " missing required keys: #{missing_keys.sort.inspect}" @@ -308,6 +310,7 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class + attr_reader :env_key alias :routes :set @@ -325,6 +328,7 @@ module ActionDispatch @prepend = [] @disable_clear_and_finalize = false @finalized = false + @env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze @set = Journey::Routes.new @router = Journey::Router.new @set diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index dca86858cc..eb554ec383 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -184,12 +184,6 @@ module ActionDispatch def _routes_context self end - - private - - def _generate_paths_by_default - true - end end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2fe37c5bd4..f7f898288b 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -388,16 +388,8 @@ module ActionDispatch APP_SESSIONS = {} - attr_reader :app - - def before_setup - super - @app = nil - @integration_session = nil - end - - def integration_session - @integration_session ||= create_session(app) + def app + @app ||= nil end # Reset the current session. This is useful for testing multiple sessions @@ -425,6 +417,8 @@ module ActionDispatch %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| + reset! unless integration_session + # reset the html_document variable, except for cookies/assigns calls unless method == 'cookies' || method == 'assigns' @html_document = nil @@ -456,16 +450,19 @@ module ActionDispatch # Copy the instance variables from the current session instance into the # test instance. def copy_session_variables! #:nodoc: + return unless integration_session @controller = @integration_session.controller @response = @integration_session.response @request = @integration_session.request end def default_url_options + reset! unless integration_session integration_session.default_url_options end def default_url_options=(options) + reset! unless integration_session integration_session.default_url_options = options end @@ -475,6 +472,7 @@ module ActionDispatch # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) + reset! unless integration_session if integration_session.respond_to?(sym) integration_session.__send__(sym, *args, &block).tap do copy_session_variables! @@ -483,6 +481,11 @@ module ActionDispatch super end end + + private + def integration_session + @integration_session ||= nil + end end end @@ -659,6 +662,7 @@ module ActionDispatch end def url_options + reset! unless integration_session integration_session.url_options end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 918589f916..f5dd9d76af 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -95,7 +95,7 @@ end module ActiveSupport class TestCase include ActionDispatch::DrawOnce - if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0 + if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 parallelize_me! end end @@ -479,7 +479,7 @@ class ForkingExecutor end end -if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0 +if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 # Use N processes (N defaults to 4) Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT) end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 26b94f0db8..a1ce12a13e 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -1074,6 +1074,14 @@ class YieldingAroundFiltersTest < ActionController::TestCase end end + def test_deprecated_skip_filter + assert_deprecated do + Class.new(PostsController) do + skip_filter :clean_up + end + end + end + protected def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 7fd1276e98..0c65270ec1 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -276,6 +276,8 @@ module ActionController end def test_async_stream + rubinius_skip "https://github.com/rubinius/rubinius/issues/2934" + @controller.latch = ActiveSupport::Concurrency::Latch.new parts = ['hello', 'world'] diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 7921f05688..a867aee7ec 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -305,7 +305,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 500 assert_select '#Application-Trace' do - assert_select 'pre code', /\(eval\):1: syntax error, unexpected/ + assert_select 'pre code', /syntax error, unexpected/ end end @@ -332,7 +332,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 500 assert_select '#Application-Trace' do - assert_select 'pre code', /\(eval\):1: syntax error, unexpected/ + assert_select 'pre code', /syntax error, unexpected/ end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 5fbd19acdf..f4c0b421d6 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -172,7 +172,7 @@ class ResponseTest < ActiveSupport::TestCase original = ActionDispatch::Response.default_charset begin ActionDispatch::Response.default_charset = 'utf-16' - resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" }) + resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" }, default_headers: nil) assert_equal('utf-16', resp.charset) ensure ActionDispatch::Response.default_charset = original @@ -254,6 +254,10 @@ class ResponseTest < ActiveSupport::TestCase end class ResponseIntegrationTest < ActionDispatch::IntegrationTest + def app + @app + end + test "response cache control from railsish app" do @app = lambda { |env| ActionDispatch::Response.new.tap { |resp| diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 3d3d4b74ae..3df022c64b 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -26,14 +26,6 @@ module ActionDispatch inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n") end - def test_json_regexp_converter - @set.draw do - get '/cart', :to => 'cart#show' - end - route = ActionDispatch::Routing::RouteWrapper.new(@set.routes.first) - assert_equal "^\\/cart(?:\\.([^\\/.?]+))?$", route.json_regexp - end - def test_displaying_routes_for_engines engine = Class.new(Rails::Engine) do def self.inspect diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index d65d2e8e8f..b4502c19d6 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3492,11 +3492,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest mount lambda { |env| [200, {}, [env['REQUEST_METHOD']]] }, at: '/' end - # HEAD request matches `get /home` rather than the lower-precedence - # Rack app mounted at `/` + # TODO: HEAD request should match `get /home` rather than the + # lower-precedence Rack app mounted at `/`. head '/home' - assert_response :success - assert_equal 'test#index', @response.body + assert_response :ok + #assert_equal 'test#index', @response.body + assert_equal 'HEAD', @response.body # But the Rack app can still respond to its own HEAD requests. head '/foobar' diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb index 6ff055af1d..b54d961f66 100644 --- a/actionpack/test/journey/routes_test.rb +++ b/actionpack/test/journey/routes_test.rb @@ -58,7 +58,6 @@ module ActionDispatch end def test_first_name_wins - #def add_route app, path, conditions, defaults, name = nil routes = Routes.new one = Path::Pattern.from_string '/hello' diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 86e20ad202..101f1263d9 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,28 @@ +* Fixed the translation helper method to accept different default values types + besides String. + + *Ulisses Almeida* + +* Collection rendering automatically caches and fetches multiple partials. + + Collections rendered as: + + ```ruby + <%= render @notifications %> + <%= render partial: 'notifications/notification', collection: @notifications, as: :notification %> + ``` + + will now read several partials from cache at once, if the template starts with a cache call: + + ```ruby + # notifications/_notification.html.erb + <% cache notification do %> + <%# ... %> + <% end %> + ``` + + *Kasper Timm Hansen* + * Fixed a dependency tracker bug that caused template dependencies not count layouts as dependencies for partials. diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index b76b35bf3c..b0793d0b91 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1246,7 +1246,7 @@ module ActionView # Admin: <%= person_form.check_box :admin %> # <% end %> # - # In the above block, the a +FormBuilder+ object is yielded as the + # In the above block, a +FormBuilder+ object is yielded as the # +person_form+ variable. This allows you to generate the +text_field+ # and +check_box+ fields by specifying their eponymous methods, which # modify the underlying template and associates the +@person+ model object @@ -1267,6 +1267,7 @@ module ActionView # ) # ) # end + # end # # The above code creates a new method +div_radio_button+ which wraps a div # around the new radio button. Note that when options are passed in, you diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 342361217c..24b633c5bb 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -37,8 +37,12 @@ module ActionView # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options = options.dup + has_default = options.has_key?(:default) remaining_defaults = Array(options.delete(:default)) - options[:default] = remaining_defaults.shift if remaining_defaults.first.kind_of? String + + if has_default && !remaining_defaults.first.kind_of?(Symbol) + options[:default] = remaining_defaults.shift + end # If the user has explicitly decided to NOT raise errors, pass that option to I18n. # Otherwise, tell I18n to raise an exception, which we rescue further in this method. diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index f281333a41..0371db07dc 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -130,5 +130,11 @@ module ActionView controller.optimize_routes_generation? : super end protected :optimize_routes_generation? + + private + + def _generate_paths_by_default + true + end end end diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 6b51775cf3..34b2698c7f 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -208,7 +208,7 @@ class PolymorphicRoutesTest < ActionController::TestCase @series.save polymorphic_url([nil, @series]) end - assert_match(/undefined method `series_url' for/, exception.message) + assert_match(/undefined method `series_url'/, exception.message) end end diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 8fde478ac9..ef4d13efa7 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -180,6 +180,11 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal 'A Generic String', translation end + def test_translate_with_object_default + translation = translate(:'translations.missing', default: 123) + assert_equal 123, translation + end + def test_translate_with_array_of_string_defaults translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string']) assert_equal 'A Generic String', translation diff --git a/activejob/README.md b/activejob/README.md index 8c83d3669a..5170ebee6e 100644 --- a/activejob/README.md +++ b/activejob/README.md @@ -7,7 +7,7 @@ small units of work and run in parallel, really. It also serves as the backend for Action Mailer's #deliver_later functionality that makes it easy to turn any mailing into a job for running later. That's -one of the most common jobs in a modern web application: Sending emails outside +one of the most common jobs in a modern web application: sending emails outside of the request-response cycle, so the user doesn't have to wait on it. The main point is to ensure that all Rails apps will have a job infrastructure @@ -118,7 +118,7 @@ Active Job is released under the MIT license: ## Support -API documentation is at +API documentation is at: * http://api.rubyonrails.org diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 58fe08cc11..8aa1b6f664 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -50,6 +50,7 @@ module ActiveModel eager_autoload do autoload :Errors autoload :StrictValidationFailed, 'active_model/errors' + autoload :UnknownAttributeError, 'active_model/errors' end module Serializers diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb index 356421476c..087d11f708 100644 --- a/activemodel/lib/active_model/attribute_assignment.rb +++ b/activemodel/lib/active_model/attribute_assignment.rb @@ -48,16 +48,5 @@ module ActiveModel raise UnknownAttributeError.new(self, k) end end - - # Raised when unknown attributes are supplied via mass assignment. - class UnknownAttributeError < NoMethodError - attr_reader :record, :attribute - - def initialize(record, attribute) - @record = record - @attribute = attribute - super("unknown attribute '#{attribute}' for #{@record.class}.") - end - end end end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 8334747615..f324788979 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/object/deep_dup' -require 'active_support/deprecation' module ActiveModel # == Active \Model \Errors @@ -525,4 +524,15 @@ module ActiveModel # # => ActiveModel::StrictValidationFailed: Name can't be blank class StrictValidationFailed < StandardError end + + # Raised when unknown attributes are supplied via mass assignment. + class UnknownAttributeError < NoMethodError + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute + super("unknown attribute '#{attribute}' for #{@record.class}.") + end + end end diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb index 402caf21f7..3b01644dd1 100644 --- a/activemodel/test/cases/attribute_assignment_test.rb +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -49,7 +49,7 @@ class AttributeAssignmentTest < ActiveModel::TestCase test "assign non-existing attribute" do model = Model.new - error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + error = assert_raises(ActiveModel::UnknownAttributeError) do model.assign_attributes(hz: 1) end @@ -58,8 +58,10 @@ class AttributeAssignmentTest < ActiveModel::TestCase end test "assign private attribute" do + rubinius_skip "https://github.com/rubinius/rubinius/issues/3328" + model = Model.new - assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + assert_raises(ActiveModel::UnknownAttributeError) do model.assign_attributes(metadata: { a: 1 }) end end diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 4ce6103593..3276f69f09 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -14,6 +14,15 @@ require 'active_support/testing/autorun' require 'mocha/setup' # FIXME: stop using mocha +# Skips the current run on Rubinius using Minitest::Assertions#skip +def rubinius_skip(message = '') + skip message if RUBY_ENGINE == 'rbx' +end +# Skips the current run on JRuby using Minitest::Assertions#skip +def jruby_skip(message = '') + skip message if defined?(JRUBY_VERSION) +end + # FIXME: we have tests that depend on run order, we should fix that and # remove this method call. require 'active_support/test_case' diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index 9a8d873ec9..3017f3541b 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -70,7 +70,7 @@ class ModelTest < ActiveModel::TestCase end def test_mixin_initializer_when_args_dont_exist - assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + assert_raises(ActiveModel::UnknownAttributeError) do SimpleModel.new(hello: 'world') end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index b66db6a754..b5dd19988d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,17 @@ +* Dont enroll records in the transaction if they dont have commit callbacks. + That was causing a memory grow problem when creating a lot of records inside a transaction. + + Fixes #15549. + + *Will Bryant*, *Aaron Patterson* + +* Correctly create through records when created on a has many through + association when using `where`. + + Fixes #19073. + + *Sean Griffin* + * Add `SchemaMigration.create_table` support any unicode charsets for MySQL. *Ryuta Kamizono* diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index f2b44913db..ee0bb8fafe 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -13,6 +13,19 @@ module ActiveRecord other == to_a end + def build(*args, &block) + scoping { @association.build(*args, &block) } + end + alias new build + + def create(*args, &block) + scoping { @association.create(*args, &block) } + end + + def create!(*args, &block) + scoping { @association.create!(*args, &block) } + end + private def exec_queries diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 82a7c27799..0ba03338f6 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -129,11 +129,11 @@ module ActiveRecord first_nth_or_last(:last, *args) end - def take + def take(n = nil) if loaded? - target.first + n ? target.take(n) : target.first else - scope.take.tap do |record| + scope.take(n).tap do |record| set_inverse_instance record if record.is_a? ActiveRecord::Base end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 87e80e88b2..e11c9490b7 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -227,8 +227,8 @@ module ActiveRecord @association.last(*args) end - def take - @association.take + def take(n = nil) + @association.take(n) end # Returns a new object of the collection type that has been instantiated diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 483d4200bd..cc265e2af6 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -29,6 +29,13 @@ module ActiveRecord assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end + # Re-raise with the ActiveRecord constant in case of an error + def _assign_attribute(k, v) # :nodoc: + super + rescue ActiveModel::UnknownAttributeError + raise UnknownAttributeError.new(self, k) + end + # Assign any deferred nested attributes after the base attributes have been set. def assign_nested_parameter_attributes(pairs) pairs.each { |k, v| _assign_attribute(k, v) } 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 0acc815d51..8231f44b4e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -401,7 +401,7 @@ module ActiveRecord # Adds a reference. Optionally adds a +type+ column, if the # +:polymorphic+ option is provided. +references+ and +belongs_to+ - # are acceptable. The reference column will be an +integer+ by default, + # are interchangeable. The reference column will be an +integer+ by default, # the +:type+ option can be used to specify a different type. A foreign # key will be created if the +:foreign_key+ option is passed. # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 3a1e4a4a88..c74f4e8fa5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -1,13 +1,10 @@ module ActiveRecord module ConnectionAdapters class TransactionState - attr_reader :parent - VALID_STATES = Set.new([:committed, :rolledback, nil]) def initialize(state = nil) @state = state - @parent = nil end def finalized? @@ -27,7 +24,7 @@ module ActiveRecord end def set_state(state) - if !VALID_STATES.include?(state) + unless VALID_STATES.include?(state) raise ArgumentError, "Invalid transaction state: #{state}" end @state = state 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 75b9d079bd..eeb141dd1e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -514,14 +514,14 @@ module ActiveRecord sql = case type.to_s when 'binary' # PostgreSQL doesn't support limits on binary (bytea) columns. - # The hard limit is 1Gb, because of a 32-bit size field, and TOAST. + # The hard limit is 1GB, because of a 32-bit size field, and TOAST. case limit when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "No binary type has byte size #{limit}.") end when 'text' # PostgreSQL doesn't support limits on text columns. - # The hard limit is 1Gb, according to section 8.3 in the manual. + # The hard limit is 1GB, according to section 8.3 in the manual. case limit when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 984af79642..24f5849e45 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -5,7 +5,7 @@ module ActiveRecord # Establishes the connection to the database. Accepts a hash as input where # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case) - # example for regular databases (MySQL, Postgresql, etc): + # example for regular databases (MySQL, PostgreSQL, etc): # # ActiveRecord::Base.establish_connection( # adapter: "mysql", diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d41872d767..97ce4642aa 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -483,51 +483,6 @@ module ActiveRecord private - def set_transaction_state(state) # :nodoc: - @transaction_state = state - end - - def has_transactional_callbacks? # :nodoc: - !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? - end - - # Updates the attributes on this particular ActiveRecord object so that - # if it is associated with a transaction, then the state of the AR object - # will be updated to reflect the current state of the transaction - # - # The @transaction_state variable stores the states of the associated - # transaction. This relies on the fact that a transaction can only be in - # one rollback or commit (otherwise a list of states would be required) - # Each AR object inside of a transaction carries that transaction's - # TransactionState. - # - # This method checks to see if the ActiveRecord object's state reflects - # the TransactionState, and rolls back or commits the ActiveRecord object - # as appropriate. - # - # Since ActiveRecord objects can be inside multiple transactions, this - # method recursively goes through the parent of the TransactionState and - # checks if the ActiveRecord object reflects the state of the object. - def sync_with_transaction_state - update_attributes_from_transaction_state(@transaction_state, 0) - end - - def update_attributes_from_transaction_state(transaction_state, depth) - @reflects_state = [false] if depth == 0 - - if transaction_state && transaction_state.finalized? && !has_transactional_callbacks? - unless @reflects_state[depth] - restore_transaction_record_state if transaction_state.rolledback? - clear_transaction_record_state - @reflects_state[depth] = true - end - - if transaction_state.parent && !@reflects_state[depth+1] - update_attributes_from_transaction_state(transaction_state.parent, depth+1) - end - end - end - # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements # of the array, and then rescues from the possible NoMethodError. If those elements are # ActiveRecord::Base's, then this triggers the various method_missing's that we have, diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index d710d96a9a..98aee77557 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -178,10 +178,8 @@ module ActiveRecord class DangerousAttributeError < ActiveRecordError end - UnknownAttributeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( # :nodoc: - 'ActiveRecord::UnknownAttributeError', - 'ActiveModel::AttributeAssignment::UnknownAttributeError' - ) + # Raised when unknown attributes are supplied via mass assignment. + UnknownAttributeError = ActiveModel::UnknownAttributeError # Raised when an error occurred while doing a mass assignment to an attribute through the # +attributes=+ method. The exception has an +attribute+ property that is the name of the diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 75b0e1e08d..18775caad2 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -539,12 +539,10 @@ module ActiveRecord conn.insert_fixture(row, fixture_set_name) end end - end - # Cap primary key sequences to max(pk). - if connection.respond_to?(:reset_pk_sequence!) - fixture_sets.each do |fs| - connection.reset_pk_sequence!(fs.table_name) + # Cap primary key sequences to max(pk). + if conn.respond_to?(:reset_pk_sequence!) + conn.reset_pk_sequence!(fs.table_name) end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 7c076864a3..a6176dffdb 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -96,7 +96,8 @@ module ActiveRecord # Returns true if the record is persisted, i.e. it's not a new record and it was # not destroyed, otherwise returns false. def persisted? - !(new_record? || destroyed?) + sync_with_transaction_state + !(@new_record || @destroyed) end # Saves the model. diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 4a4de86d48..7f27e7b463 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -222,7 +222,7 @@ module ActiveRecord end def execute_simple_calculation(operation, column_name, distinct) #:nodoc: - # Postgresql doesn't like ORDER BY when there are no GROUP BY + # PostgreSQL doesn't like ORDER BY when there are no GROUP BY 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 fb47d915ff..7bd091b66c 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,4 +1,3 @@ -require 'active_support/deprecation' require 'active_support/core_ext/string/filters' module ActiveRecord diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 598defce50..ffe7c4ae42 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -442,5 +442,43 @@ module ActiveRecord end end end + + private + + def set_transaction_state(state) # :nodoc: + @transaction_state = state + end + + def has_transactional_callbacks? # :nodoc: + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? + end + + # Updates the attributes on this particular ActiveRecord object so that + # if it is associated with a transaction, then the state of the AR object + # will be updated to reflect the current state of the transaction + # + # The @transaction_state variable stores the states of the associated + # transaction. This relies on the fact that a transaction can only be in + # one rollback or commit (otherwise a list of states would be required) + # Each AR object inside of a transaction carries that transaction's + # TransactionState. + # + # This method checks to see if the ActiveRecord object's state reflects + # the TransactionState, and rolls back or commits the ActiveRecord object + # as appropriate. + # + # Since ActiveRecord objects can be inside multiple transactions, this + # method recursively goes through the parent of the TransactionState and + # checks if the ActiveRecord object reflects the state of the object. + def sync_with_transaction_state + update_attributes_from_transaction_state(@transaction_state) + end + + def update_attributes_from_transaction_state(transaction_state) + if transaction_state && transaction_state.finalized? + restore_transaction_record_state if transaction_state.rolledback? + clear_transaction_record_state + end + end end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 897c52a49d..675bed9bfa 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -478,6 +478,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! } end + def test_taking_with_a_number + # taking from unloaded Relation + bob = Author.find(authors(:bob).id) + assert_equal [posts(:misc_by_bob)], bob.posts.take(1) + bob = Author.find(authors(:bob).id) + assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2) + + # taking from loaded Relation + bob.posts.to_a + assert_equal [posts(:misc_by_bob)], authors(:bob).posts.take(1) + assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], authors(:bob).posts.take(2) + end + def test_taking_with_inverse_of interests(:woodsmanship).destroy interests(:survival).destroy 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 6729a5a9fc..5f52c65412 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -595,6 +595,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb") end + def test_through_record_is_built_when_created_with_where + assert_difference("posts(:thinking).readers.count", 1) do + posts(:thinking).people.where(first_name: "Jeb").create + end + end + def test_associate_with_create_and_no_options peeps = posts(:thinking).people.count posts(:thinking).people.create(:first_name => 'foo') diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 3b6a4038cd..7dc9266074 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -276,7 +276,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_create_with_inexistent_foreign_key_failing firm = Firm.create(name: 'GlobalMegaCorp') - assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + assert_raises(ActiveRecord::UnknownAttributeError) do firm.create_account_with_inexistent_foreign_key end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 243c90e945..ea2b94cbf4 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -758,12 +758,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_bulk_update_respects_access_control privatize("title=(value)") - assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new(:title => "Rants about pants") } - assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } end def test_bulk_update_raise_unknown_attribute_error - error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) { + error = assert_raises(ActiveRecord::UnknownAttributeError) { Topic.new(hello: "world") } assert_instance_of Topic, error.record diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index e7b76b1cf9..927d7950a5 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -58,7 +58,7 @@ module ActiveRecord data = OverloadedType.new(non_existent_decimal: 1) assert_equal BigDecimal.new(1), data.non_existent_decimal - assert_raise ActiveModel::AttributeAssignment::UnknownAttributeError do + assert_raise ActiveRecord::UnknownAttributeError do UnoverloadedType.new(non_existent_decimal: 1) end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 7ef2ebc998..d1add21219 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -327,7 +327,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!) fixtures :companies def setup - @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')] + @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting'), Course.new(name: 'Test')] ActiveRecord::FixtureSet.reset_cache # make sure tables get reinitialized end diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index bea9d6b2c9..8fd08fe4ce 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -140,7 +140,7 @@ module ActiveRecord tables_after = connection.tables - tables_before tables_after.each do |table| - connection.execute "DROP TABLE #{table}" + connection.drop_table table end end end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index c0daa83e9c..1760314099 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -312,7 +312,7 @@ class MigratorTest < ActiveRecord::TestCase def test_migrator_db_has_no_schema_migrations_table _, migrator = migrator_class(3) - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") + ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations') migrator.migrate("valid", 1) assert ActiveRecord::Base.connection.table_exists?('schema_migrations') diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 198cd6f341..c5f6589c22 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -672,7 +672,7 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_not_assign_destroy_key_to_a_record - assert_nothing_raised ActiveModel::AttributeAssignment::UnknownAttributeError do + assert_nothing_raised ActiveRecord::UnknownAttributeError do @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }}) end end diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index a9a6514c9d..d6fd0c4ab0 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -35,9 +35,7 @@ BEGIN END SQL - ActiveRecord::Base.connection.execute <<-SQL -DROP TABLE IF EXISTS collation_tests; -SQL + ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true ActiveRecord::Base.connection.execute <<-SQL CREATE TABLE collation_tests ( @@ -46,9 +44,7 @@ CREATE TABLE collation_tests ( ) CHARACTER SET utf8 COLLATE utf8_general_ci SQL - ActiveRecord::Base.connection.execute <<-SQL -DROP TABLE IF EXISTS enum_tests; -SQL + ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true ActiveRecord::Base.connection.execute <<-SQL CREATE TABLE enum_tests ( diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb index f2cffca52c..b5378341b5 100644 --- a/activerecord/test/schema/mysql_specific_schema.rb +++ b/activerecord/test/schema/mysql_specific_schema.rb @@ -46,9 +46,7 @@ BEGIN END SQL - ActiveRecord::Base.connection.execute <<-SQL -DROP TABLE IF EXISTS collation_tests; -SQL + ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true ActiveRecord::Base.connection.execute <<-SQL CREATE TABLE collation_tests ( @@ -57,9 +55,7 @@ CREATE TABLE collation_tests ( ) CHARACTER SET utf8 COLLATE utf8_general_ci SQL - ActiveRecord::Base.connection.execute <<-SQL -DROP TABLE IF EXISTS enum_tests; -SQL + ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true ActiveRecord::Base.connection.execute <<-SQL CREATE TABLE enum_tests ( diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 35be823ea1..43fb87f203 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +* Added `#without` on `Enumerable` and `Array` to return a copy of an + enumerable without the specified elements. + + *Todd Bealmear* + * Fixed a problem where String#truncate_words would get stuck with a complex string. diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index ca66d806ef..3177d8498e 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -27,6 +27,18 @@ class Array end end + # Returns a copy of the Array without the specified elements. + # + # people = ["David", "Rafael", "Aaron", "Todd"] + # people.without "Aaron", "Todd" + # => ["David", "Rafael"] + # + # Note: This is an optimization of `Enumerable#without` that uses `Array#-` + # instead of `Array#reject` for performance reasons. + def without(*elements) + self - elements + end + # Equal to <tt>self[1]</tt>. # # %w( a b c d e ).second # => "b" diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 1343beb87a..7a893292b3 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -60,6 +60,17 @@ module Enumerable def exclude?(object) !include?(object) end + + # Returns a copy of the enumerable without the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd" + # => ["David", "Rafael"] + # + # {foo: 1, bar: 2, baz: 3}.without :bar + # => {foo: 1, baz: 3} + def without(*elements) + reject { |element| elements.include?(element) } + end end class Range #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb index b0c7b021db..0ecc67a855 100644 --- a/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -7,6 +7,13 @@ class Module # m = Module.new # m.name # => nil # + # +anonymous?+ method returns true if module does not have a name: + # + # Module.new.anonymous? # => true + # + # module M; end + # M.anonymous? # => false + # # A module gets a name when it is first assigned to a constant. Either # via the +module+ or +class+ keyword or by an explicit assignment: # diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index a5f4d03256..9b7a429db9 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -167,7 +167,7 @@ class Module '' end - file, line = caller.first.split(':', 2) + file, line = caller(1, 1).first.split(':', 2) line = line.to_i to = to.to_s diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index b1a12781f3..83eced50bf 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -3,9 +3,24 @@ class Range :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" } } - # Gives a human readable format of the range. + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. # - # (1..100).to_formatted_s # => "1..100" + # This method is aliased to <tt>to_s</tt>. + # + # range = (1..100) # => 1..100 + # + # range.to_formatted_s # => "1..100" + # range.to_s # => "1..100" + # + # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'" + # range.to_s(:db) # => "BETWEEN '1' AND '100'" + # + # == Adding your own range formats to to_formatted_s + # You can add your own formats to the Range::RANGE_FORMATS hash. + # Use the format name as the hash key and a Proc instance. + # + # # config/initializers/range_formats.rb + # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" } def to_formatted_s(format = :default) if formatter = RANGE_FORMATS[format] formatter.call(first, last) diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index b88976eab2..7461d03acc 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -26,6 +26,7 @@ class String # Returns a new string with all occurrences of the patterns removed. # str = "foo bar test" # str.remove(" test") # => "foo bar" + # str.remove(" test", /bar/) # => "foo " # str # => "foo bar test" def remove(*patterns) dup.remove!(*patterns) @@ -33,8 +34,8 @@ class String # Alters the string by removing all occurrences of the patterns. # str = "foo bar test" - # str.remove!(" test") # => "foo bar" - # str # => "foo bar" + # str.remove!(" test", /bar/) # => "foo " + # str # => "foo " def remove!(*patterns) patterns.each do |pattern| gsub! pattern, "" diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 2eedd4fdb1..57d7f8d1e7 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -35,6 +35,13 @@ class String ActiveSupport::Multibyte.proxy_class.new(self) end + # Return +true+ if string has utf_8 encoding. + # + # utf_8_str = "some string".encode "UTF-8" + # iso_str = "some string".encode "ISO-8859-1" + # + # utf_8_str.is_utf8? # => true + # iso_str.is_utf8? # => false def is_utf8? case encoding when Encoding::UTF_8 diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 1a02acd5b1..67aac32742 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -100,7 +100,7 @@ module ActiveSupport # a string, otherwise a NameError will be raised by the interpreter # itself when rescue_from CONSTANT is executed. klass = self.class.const_get(klass_name) rescue nil - klass ||= klass_name.constantize rescue nil + klass ||= (klass_name.constantize rescue nil) klass === exception if klass end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 095e908907..7f5f8feb0d 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -1028,7 +1028,7 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase @cache.read_multi('hello', 'world') - assert_match "Caches multi read:\n- hello\n- world", @buffer.string.tap { |l| p l } + assert_match "Caches multi read:\n- hello\n- world", @buffer.string end end diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb index f14f64421d..3f1e0c4cb4 100644 --- a/activesupport/test/core_ext/array/access_test.rb +++ b/activesupport/test/core_ext/array/access_test.rb @@ -27,4 +27,8 @@ class AccessTest < ActiveSupport::TestCase assert_equal array[4], array.fifth assert_equal array[41], array.forty_two end + + def test_without + assert_equal [1, 2, 4], [1, 2, 3, 4, 5].without(3, 5) + end end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 346dc3d208..e5d8ae7882 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -103,4 +103,11 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal true, GenericEnumerable.new([ 1 ]).exclude?(2) assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1) end + + def test_without + assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5) + assert_equal [1, 2, 4], (1..5).to_a.without(3, 5) + assert_equal [1, 2, 4], (1..5).to_set.without(3, 5) + assert_equal({foo: 1, baz: 3}, {foo: 1, bar: 2, baz: 3}.without(:bar)) + end end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index d37f4bd0d8..042f5cfb34 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -9,6 +9,9 @@ class DuplicableTest < ActiveSupport::TestCase ALLOW_DUP << BigDecimal.new('4.56') def test_duplicable + rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ + "* https://github.com/rubinius/rubinius/issues/3089" + RAISE_DUP.each do |v| assert !v.duplicable? assert_raises(TypeError, v.class.name) { v.dup } diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 63d921e3b4..2f269a66f0 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -130,6 +130,8 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_process_status + rubinius_skip "https://github.com/rubinius/rubinius/issues/3334" + # There doesn't seem to be a good way to get a handle on a Process::Status object without actually # creating a child process, hence this to populate $? system("not_a_real_program_#{SecureRandom.hex}") diff --git a/guides/Rakefile b/guides/Rakefile index fd093b94c1..3c2099ac02 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -11,7 +11,7 @@ namespace :guides do ruby "rails_guides.rb" end - desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing" + desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211" task :kindle do unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH" diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 67f4a3c02c..bd35e2d31a 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -73,7 +73,7 @@ Major Features * **Strong parameters** ([commit](https://github.com/rails/rails/commit/a8f6d5c6450a7fe058348a7f10a908352bb6c7fc)) - Only allow whitelisted parameters to update model objects (`params.permit(:title, :text)`). * **Routing concerns** ([commit](https://github.com/rails/rails/commit/0dd24728a088fcb4ae616bb5d62734aca5276b1b)) - In the routing DSL, factor out common subroutes (`comments` from `/posts/1/comments` and `/videos/1/comments`). * **ActionController::Live** ([commit](https://github.com/rails/rails/commit/af0a9f9eefaee3a8120cfd8d05cbc431af376da3)) - Stream JSON with `response.stream`. - * **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation + * **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation. * **[Russian doll caching](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object. * **Turbolinks** ([commit](https://github.com/rails/rails/commit/e35d8b18d0649c0ecc58f6b73df6b3c8d0c6bb74)) - Serve only one initial HTML page. When the user navigates to another page, use pushState to update the URL and use AJAX to update the title and body. * **Decouple ActionView from ActionController** ([commit](https://github.com/rails/rails/commit/78b0934dd1bb84e8f093fb8ef95ca99b297b51cd)) - ActionView was decoupled from ActionPack and will be moved to a separated gem in Rails 4.1. @@ -181,7 +181,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a * `String#to_date` now raises `ArgumentError: invalid date` instead of `NoMethodError: undefined method 'div' for nil:NilClass` when given an invalid date. It is now the same as `Date.parse`, and it accepts more invalid dates than 3.x, such as: - ``` + ```ruby # ActiveSupport 3.x "asdf".to_date # => NoMethodError: undefined method `div' for nil:NilClass "333".to_date # => NoMethodError: undefined method `div' for nil:NilClass diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 8d5557be6e..6bf65757ec 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -317,15 +317,15 @@ for detailed changes. * Removed deprecated constants from Action Controller: - | Removed | Successor | - |:-----------------------------------|:--------------------------------| - | ActionController::AbstractRequest | ActionDispatch::Request | - | ActionController::Request | ActionDispatch::Request | - | ActionController::AbstractResponse | ActionDispatch::Response | - | ActionController::Response | ActionDispatch::Response | - | ActionController::Routing | ActionDispatch::Routing | - | ActionController::Integration | ActionDispatch::Integration | - | ActionController::IntegrationTest | ActionDispatch::IntegrationTest | +| Removed | Successor | +|:-----------------------------------|:--------------------------------| +| ActionController::AbstractRequest | ActionDispatch::Request | +| ActionController::Request | ActionDispatch::Request | +| ActionController::AbstractResponse | ActionDispatch::Response | +| ActionController::Response | ActionDispatch::Response | +| ActionController::Routing | ActionDispatch::Routing | +| ActionController::Integration | ActionDispatch::Integration | +| ActionController::IntegrationTest | ActionDispatch::IntegrationTest | ### Notable changes diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index d65c15bcf3..f19934fe89 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -917,8 +917,8 @@ write your own validators or validation methods as you prefer. ### Custom Validators -Custom validators are classes that extend `ActiveModel::Validator`. These -classes must implement a `validate` method which takes a record as an argument +Custom validators are classes that inherit from `ActiveModel::Validator`. These +classes must implement the `validate` method which takes a record as an argument and performs the validation on it. The custom validator is called using the `validates_with` method. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 0fbd6ed7e1..66626f41d1 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -349,7 +349,7 @@ end we get: ```ruby -current_user.to_query('user') # => user=357-john-smith +current_user.to_query('user') # => "user=357-john-smith" ``` This method escapes whatever is needed, both for the key and the value: @@ -727,7 +727,7 @@ NOTE: Defined in `active_support/core_ext/module/introspection.rb`. #### Qualified Constant Names -The standard methods `const_defined?`, `const_get` , and `const_set` accept +The standard methods `const_defined?`, `const_get`, and `const_set` accept bare constant names. Active Support extends this API to be able to pass relative qualified constant names. @@ -1237,7 +1237,7 @@ Calling `dup` or `clone` on safe strings yields safe strings. The method `remove` will remove all occurrences of the pattern: ```ruby -"Hello World".remove(/Hello /) => "World" +"Hello World".remove(/Hello /) # => "World" ``` There's also the destructive version `String#remove!`. @@ -2182,6 +2182,17 @@ to_visit << node if visited.exclude?(node) NOTE: Defined in `active_support/core_ext/enumerable.rb`. +### `without` + +The method `without` returns a copy of an enumerable with the specified elements +removed: + +```ruby +people.without("Aaron", "Todd") +``` + +NOTE: Defined in `active_support/core_ext/enumerable.rb`. + Extensions to `Array` --------------------- diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 9d9f40e956..352da43b5f 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -218,7 +218,7 @@ Action View ```ruby { - identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb", + identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb" } ``` @@ -307,7 +307,7 @@ Action Mailer } ``` -ActiveResource +Active Resource -------------- ### request.active_resource diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 52ea605a72..6f8b4f4d15 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -729,27 +729,6 @@ include, you can add them to the `precompile` array in `config/initializers/asse 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/initializers/assets.rb -Rails.application.config.assets.precompile << Proc.new do |path| - if path =~ /\.(css|js)\z/ - full_path = Rails.application.assets.resolve(path).to_path - app_assets_path = Rails.root.join('app', 'assets').to_path - if full_path.starts_with? app_assets_path - logger.info "including asset: " + full_path - true - else - logger.info "excluding asset: " + full_path - false - end - else - false - end -end -``` - NOTE. Always specify an expected compiled filename that ends with .js or .css, even if you want to add Sass or CoffeeScript files to the precompile array. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index d6b2e75e1e..280c3008e9 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -460,7 +460,7 @@ class CreatePictures < ActiveRecord::Migration t.timestamps null: false end - add_index :pictures, :imageable_id + add_index :pictures, [:imageable_type, :imageable_id] end end ``` diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 0fa20f7ccf..716beb9178 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -32,7 +32,7 @@ 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://signalvnoise.com/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). ### Action Caching diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 19ccdc5488..6f5a6b7957 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -338,6 +338,12 @@ You can specify the environment in which the `runner` command should operate usi $ bin/rails runner -e staging "Model.long_running_method" ``` +You can even execute ruby code written in a file with runner. + +```bash +$ bin/rails runner lib/code_to_be_run.rb +``` + ### `rails destroy` Think of `destroy` as the opposite of `generate`. It'll figure out what generate did, and undo it. diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 926a048762..9cb2efb9a2 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -242,55 +242,6 @@ 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 `web-console` gem -------------------------------------- - -The web console allows you to start an interactive Ruby session in your browser. -An interactive console is launched automatically in case of an error but can also -be launched for debugging purposes by invoking `console` in a view or controller. - -For example in a view: - -```ruby -# new.html.erb -<%= console %> -``` - -Or in a controller: - -```ruby -# posts_controller.rb -class PostsController < ApplicationController - def new - console - @post = Post.new - end -end -``` - -### config.web_console.whitelisted_ips - -By default the web console can only be accessed from localhost. -`config.web_console.whitelisted_ips` lets you control which IPs have access to -the console. - -For example, to allow access from both localhost and 192.168.0.100, you can put -inside your configuration file: - -```ruby -config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.100 ) -``` - -Or to allow access from an entire network: - -```ruby -config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.0/16 ) -``` - -The web console is a powerful tool so be careful when you give access to an IP. - - Debugging with the `byebug` gem --------------------------------- @@ -831,10 +782,10 @@ will be stopped and you will have to start it again. `byebug` has a few available options to tweak its behaviour: -* `set autoreload`: Reload source code when changed (default: true). -* `set autolist`: Execute `list` command on every breakpoint (default: true). +* `set autoreload`: Reload source code when changed (defaults: true). +* `set autolist`: Execute `list` command on every breakpoint (defaults: true). * `set listsize _n_`: Set number of source lines to list by default to _n_ -(default: 10) +(defaults: 10) * `set forcestep`: Make sure the `next` and `step` commands always move to a new line. @@ -849,6 +800,63 @@ set forcestep set listsize 25 ``` +Debugging with the `web-console` gem +------------------------------------ + +Web Console is a bit like `byebug`, but it runs in the browser. In any page you +are developing, you can request a console in the context of a view or a +controller. The console would be rendered next to your HTML content. + +### Console + +Inside any controller action or view, you can then invoke the console by +calling the `console` method. + +For example, in a controller: + +```ruby +class PostController < ApplicationController + def new + console + @post = Post.new + end +end +``` + +Or in a view: + +```html+erb +<% console %> + +<h2>New Post</h2> +``` + +This will render a console inside your view. You don't need to care about the +location of the `console` call; it won't be rendered on the spot of its +invocation but next to your HTML content. + +The console executes pure Ruby code. You can define and instantiate +custom classes, create new models and inspect variables. + +NOTE: Only one console can be rendered per request. Otherwise `web-console` +will raise an error on the second `console` invocation. + +### Inspecting Variables + +You can invoke `instance_variables` to list all the instance variables +available in your context. If you want to list all the local variables, you can +do that with `local_variables`. + +### Settings + +* `config.web_console.whitelisted_ips`: Authorized list of IPv4 or IPv6 +addresses and networks (defaults: `127.0.0.1/8, ::1`). +* `config.web_console.whiny_requests`: Log a message when a console rendering +is prevented (defaults: `true`). + +Since `web-console` evaluates plain Ruby code remotely on the server, don't try +to use it in production. + Debugging Memory Leaks ---------------------- @@ -905,6 +913,7 @@ 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) +* [web-console Homepage](https://github.com/rails/web-console) * [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/form_helpers.md b/guides/source/form_helpers.md index 90004c611b..c21a2ba613 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -729,7 +729,7 @@ The two basic structures are arrays and hashes. Hashes mirror the syntax used fo the `params` hash will contain -```erb +```ruby {'person' => {'name' => 'Henry'}} ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 51b8a2ca5f..66cfb72aaf 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1479,13 +1479,14 @@ Finally, add a 'Destroy' link to your `index` action template ``` Here we're using `link_to` in a different way. We pass the named route as the -second argument, and then the options as another argument. The `:method` and -`:'data-confirm'` options are used as HTML5 attributes so that when the link is -clicked, Rails will first show a confirm dialog to the user, and then submit the -link with method `delete`. This is done via the JavaScript file `jquery_ujs` -which is automatically included into your application's layout -(`app/views/layouts/application.html.erb`) when you generated the application. -Without this file, the confirmation dialog box wouldn't appear. +second argument, and then the options as another argument. The `method: :delete` +and `data: { confirm: 'Are you sure?' }` options are used as HTML5 attributes so +that when the link is clicked, Rails will first show a confirm dialog to the +user, and then submit the link with method `delete`. This is done via the +JavaScript file `jquery_ujs` which is automatically included in your +application's layout (`app/views/layouts/application.html.erb`) when you +generated the application. Without this file, the confirmation dialog box won't +appear. ![Confirm Dialog](images/getting_started/confirm_dialog.png) diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 4dc4c5a660..1e34261272 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -1093,11 +1093,8 @@ Resources Authors ------- -* [Sven Fuchs](http://www.workingwithrails.com/person/9963-sven-fuchs) (initial author) -* [Karel Minařík](http://www.workingwithrails.com/person/7476-karel-mina-k) - -If you found this guide useful, please consider recommending its authors on [workingwithrails](http://www.workingwithrails.com). - +* [Sven Fuchs](http://svenfuchs.com) (initial author) +* [Karel Minařík](http://www.karmi.cz) Footnotes --------- diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index b0e71035c0..5c7fad09ed 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -565,7 +565,7 @@ In this application: ##### Template Inheritance -Similarly to the Layout Inheritance logic, if a template or partial is not found in the conventional path, the controller will look for a template or partial to render in its inheritance chain. For example: +Similar to the Layout Inheritance logic, if a template or partial is not found in the conventional path, the controller will look for a template or partial to render in its inheritance chain. For example: ```ruby # in app/controllers/application_controller diff --git a/guides/source/routing.md b/guides/source/routing.md index a689e131ff..b5defc9d20 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -229,7 +229,7 @@ or, for a single case: resources :articles, path: '/admin/articles' ``` -In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`: +In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `ArticlesController`: | HTTP Verb | Path | Controller#Action | Named Helper | | --------- | ------------------------ | -------------------- | ---------------------- | diff --git a/guides/source/testing.md b/guides/source/testing.md index 14bc75aa7d..cb3bd68fbe 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -450,6 +450,7 @@ All the basic assertions such as `assert_equal` defined in `Minitest::Assertions * `ActionMailer::TestCase` * `ActionView::TestCase` * `ActionDispatch::IntegrationTest` +* `ActiveJob::TestCase` Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 8306233dcd..8b10144413 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,10 @@ +* Created rake restart task. Restarts your Rails app by touching the + `tmp/restart.txt`. + + Fixes #18876. + + *Hyonjee Joo* + * Set Rails console to use log formatter and log level as specified for the given environment. diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 0bdf63943f..583d005d46 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -71,12 +71,12 @@ class CodeStatistics #:nodoc: def print_header print_splitter - puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" + puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" print_splitter end def print_splitter - puts "+----------------------+-------+-------+---------+---------+-----+-------+" + puts "+----------------------+--------+--------+---------+---------+-----+-------+" end def print_line(name, statistics) @@ -84,8 +84,8 @@ class CodeStatistics #:nodoc: loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 puts "| #{name.ljust(20)} " \ - "| #{statistics.lines.to_s.rjust(5)} " \ - "| #{statistics.code_lines.to_s.rjust(5)} " \ + "| #{statistics.lines.to_s.rjust(6)} " \ + "| #{statistics.code_lines.to_s.rjust(6)} " \ "| #{statistics.classes.to_s.rjust(7)} " \ "| #{statistics.methods.to_s.rjust(7)} " \ "| #{m_over_c.to_s.rjust(3)} " \ diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index e1d5caf790..fd37fd0457 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -513,7 +513,7 @@ module Rails def call(env) env.merge!(env_config) if env['SCRIPT_NAME'] - env["ROUTES_#{routes.object_id}_SCRIPT_NAME"] = env['SCRIPT_NAME'].dup + env[routes.env_key] = env['SCRIPT_NAME'].dup end app.call(env) end diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 49e5431a16..6e61cc3cb5 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -17,7 +17,28 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc: end def routes - @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes) - @page_title = 'Routes' + if path = params[:path] + path = URI.escape path + normalized_path = with_leading_slash path + render json: { + exact: match_route {|it| it.match normalized_path }, + fuzzy: match_route {|it| it.spec.to_s.match path } + } + else + @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes) + @page_title = 'Routes' + end + end + + private + + def match_route + _routes.routes.select {|route| + yield route.path + }.map {|route| route.path.spec.to_s } + end + + def with_leading_slash(path) + ('/' + path).squeeze('/') end end diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index aea3d2339c..e2a199ec4d 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,4 +1,4 @@ -if RUBY_VERSION < '2.2.0' +if RUBY_VERSION < '2.2.0' && RUBY_ENGINE == 'ruby' desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index 945fbdb3e2..a5e4d2935e 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -7,6 +7,7 @@ require 'rake' log middleware misc + restart routes statistics tmp diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake new file mode 100644 index 0000000000..1e8940b675 --- /dev/null +++ b/railties/lib/rails/tasks/restart.rake @@ -0,0 +1,4 @@ +desc "Restart app by touching tmp/restart.txt" +task :restart do + FileUtils.touch('tmp/restart.txt') +end diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb new file mode 100644 index 0000000000..35099913fb --- /dev/null +++ b/railties/test/application/rake/restart_test.rb @@ -0,0 +1,31 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeRestartTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test 'rake restart touches tmp/restart.txt' do + Dir.chdir(app_path) do + `rake restart` + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + sleep(1) + `rake restart` + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime + end + end + end + end +end diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 25b0e761cb..d87b51d852 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -53,4 +53,29 @@ class InfoControllerTest < ActionController::TestCase assert_response :success end + test "info controller returns exact matches" do + exact_count = -> { JSON(response.body)['exact'].size } + + get :routes, path: 'rails/info/route' + assert exact_count.call == 0, 'should not match incomplete routes' + + get :routes, path: 'rails/info/routes' + assert exact_count.call == 1, 'should match complete routes' + + get :routes, path: 'rails/info/routes.html' + assert exact_count.call == 1, 'should match complete routes with optional parts' + end + + test "info controller returns fuzzy matches" do + fuzzy_count = -> { JSON(response.body)['fuzzy'].size } + + get :routes, path: 'rails/info' + assert fuzzy_count.call == 2, 'should match incomplete routes' + + get :routes, path: 'rails/info/routes' + assert fuzzy_count.call == 1, 'should match complete routes' + + get :routes, path: 'rails/info/routes.html' + assert fuzzy_count.call == 0, 'should match optional parts of route literally' + end end |