aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md8
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb122
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb26
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb24
-rw-r--r--actionpack/test/dispatch/response_test.rb4
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb8
-rw-r--r--actionpack/test/dispatch/routing_test.rb9
-rw-r--r--actionview/CHANGELOG.md20
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb3
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/association_relation.rb13
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb6
-rw-r--r--guides/source/active_support_core_extensions.md2
-rw-r--r--guides/source/command_line.md6
-rw-r--r--railties/lib/rails/info_controller.rb25
-rw-r--r--railties/test/rails_info_controller_test.rb25
18 files changed, 201 insertions, 116 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 1083f4a92d..0134bf7cab 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -16,6 +16,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/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index e9df984c86..2b036796ab 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/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/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/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 5fbd19acdf..c61423dce4 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -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/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 86e20ad202..8f47171e62 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,23 @@
+* 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/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index b66db6a754..8d76b4145b 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,10 @@
+* 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/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/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 0fbd6ed7e1..0594b701dc 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -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.
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/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/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