aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md10
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb27
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb34
-rw-r--r--activerecord/CHANGELOG.md20
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb39
-rw-r--r--activerecord/test/cases/finder_test.rb32
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb56
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb6
-rw-r--r--activesupport/test/share_lock_test.rb78
-rw-r--r--guides/source/5_0_release_notes.md3
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md8
-rw-r--r--guides/source/routing.md17
-rw-r--r--railties/lib/rails/application/finisher.rb2
-rw-r--r--railties/lib/rails/tasks/routes.rake32
-rw-r--r--railties/test/application/rake_test.rb63
17 files changed, 323 insertions, 115 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index f6ffe45490..b2d9288eca 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options
+ to `bin/rake routes`. These options return the url `name`, `verb` and
+ `path` field that match the pattern or match a specific controller.
+
+ Deprecate `CONTROLLER` env variable in `bin/rake routes`.
+
+ See #18902.
+
+ *Anton Davydov* & *Vipul A M*
+
* Response etags to always be weak: Prefixes 'W/' to value returned by
`ActionDispatch::Http::Cache::Response#etag=`, such that etags set in
`fresh_when` and `stale?` are weak.
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0f7898a3f8..4bd727c14e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -80,6 +80,14 @@ module ActionDispatch
set_header DATE, utc_time.httpdate
end
+ # This method allows you to set the ETag for cached content, which
+ # will be returned to the end user.
+ #
+ # By default, Action Dispatch sets all ETags to be weak.
+ # This ensures that if the content changes only semantically,
+ # the whole page doesn't have to be regenerated from scratch
+ # by the web server. With strong ETags, pages are compared
+ # byte by byte, and are regenerated only if they are not exactly equal.
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
super %(W/"#{Digest::MD5.hexdigest(key)}")
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index d00b2c3eb5..6cde5b2900 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -239,7 +239,8 @@ module ActionDispatch
#
# rails routes
#
- # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
+ # Target specific controllers by prefixing the command with <tt>--controller</tt> option
+ # - or its <tt>-c</tt> shorthand.
#
module Routing
extend ActiveSupport::Autoload
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 69e6dd5215..b806ee015b 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -60,12 +60,10 @@ module ActionDispatch
end
def format(formatter, filter = nil)
- routes_to_display = filter_routes(filter)
-
+ routes_to_display = filter_routes(normalize_filter(filter))
routes = collect_routes(routes_to_display)
-
if routes.none?
- formatter.no_routes(collect_routes(@routes), filter)
+ formatter.no_routes(collect_routes(@routes))
return formatter.result
end
@@ -82,10 +80,19 @@ module ActionDispatch
private
+ def normalize_filter(filter)
+ if filter.is_a?(Hash) && filter[:controller]
+ { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
+ elsif filter
+ { controller: /#{filter}/, action: /#{filter}/ }
+ end
+ end
+
def filter_routes(filter)
if filter
- filter_name = filter.underscore.sub(/_controller$/, '')
- @routes.select { |route| route.defaults[:controller] == filter_name }
+ @routes.select do |route|
+ filter.any? { |default, value| route.defaults[default] =~ value }
+ end
else
@routes
end
@@ -137,7 +144,7 @@ module ActionDispatch
@buffer << draw_header(routes)
end
- def no_routes(routes, filter)
+ def no_routes(routes)
@buffer <<
if routes.none?
<<-MESSAGE.strip_heredoc
@@ -145,8 +152,6 @@ module ActionDispatch
Please add some routes in config/routes.rb.
MESSAGE
- elsif missing_controller?(filter)
- "The controller #{filter} does not exist!"
else
"No routes were found for this controller"
end
@@ -154,10 +159,6 @@ module ActionDispatch
end
private
- def missing_controller?(controller_name)
- [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize)
- end
-
def draw_section(routes)
header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 7382c267c7..f72a87b994 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -17,10 +17,10 @@ module ActionDispatch
@set = ActionDispatch::Routing::RouteSet.new
end
- def draw(options = {}, &block)
+ def draw(options = nil, &block)
@set.draw(&block)
inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes)
- inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
+ inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options).split("\n")
end
def test_displaying_routes_for_engines
@@ -297,7 +297,7 @@ module ActionDispatch
end
def test_routes_can_be_filtered
- output = draw(filter: 'posts') do
+ output = draw('posts') do
resources :articles
resources :posts
end
@@ -313,6 +313,26 @@ module ActionDispatch
" DELETE /posts/:id(.:format) posts#destroy"], output
end
+ def test_routes_can_be_filtered_with_namespaced_controllers
+ output = draw('admin/posts') do
+ resources :articles
+ namespace :admin do
+ resources :posts
+ end
+ end
+
+ assert_equal [" Prefix Verb URI Pattern Controller#Action",
+ " admin_posts GET /admin/posts(.:format) admin/posts#index",
+ " POST /admin/posts(.:format) admin/posts#create",
+ " new_admin_post GET /admin/posts/new(.:format) admin/posts#new",
+ "edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit",
+ " admin_post GET /admin/posts/:id(.:format) admin/posts#show",
+ " PATCH /admin/posts/:id(.:format) admin/posts#update",
+ " PUT /admin/posts/:id(.:format) admin/posts#update",
+ " DELETE /admin/posts/:id(.:format) admin/posts#destroy"], output
+ end
+
+
def test_regression_route_with_controller_regexp
output = draw do
get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
@@ -336,18 +356,18 @@ module ActionDispatch
end
def test_routes_with_undefined_filter
- output = draw(:filter => 'Rails::MissingController') do
+ output = draw(controller: 'Rails::MissingController') do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
assert_equal [
- "The controller Rails::MissingController does not exist!",
+ "No routes were found for this controller",
"For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
], output
end
def test_no_routes_matched_filter
- output = draw(:filter => 'rails/dummy') do
+ output = draw('rails/dummy') do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
@@ -358,7 +378,7 @@ module ActionDispatch
end
def test_no_routes_were_defined
- output = draw(:filter => 'Rails::DummyController') { }
+ output = draw('Rails::DummyController') {}
assert_equal [
"You don't have any routes defined!",
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index faab0f9554..50a0b291b3 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,23 +1,3 @@
-* Rework `ActiveRecord::Relation#last`
-
- 1. Always find last with ruby if relation is loaded
- 2. Always use SQL instead if relation is not loaded.
- 3. Deprecated relation loading when SQL order can not be automatically reversed
-
- Topic.order("title").load.last(3)
- # before: SELECT ...
- # after: No SQL
-
- Topic.order("title").last
- # before: SELECT * FROM `topics`
- # after: SELECT * FROM `topics` ORDER BY `topics`.`title` DESC LIMIT 1
-
- Topic.order("coalesce(author, title)").last
- # before: SELECT * FROM `topics`
- # after: Deprecation Warning for irreversible order
-
- *Bogdan Gusiev*
-
* `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError`
when the order can not be reversed using current trivial algorithm.
Also raises the same error when `#reverse_order` is called on
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 5d4a045097..3f5d6de78a 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -145,23 +145,15 @@ module ActiveRecord
#
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
- return find_last(limit) if loaded?
-
- result = order_values.empty? && primary_key ? order(arel_table[primary_key].desc) : reverse_order
- result = result.limit!(limit || 1)
- limit ? result.reverse : result.first
- rescue ActiveRecord::IrreversibleOrderError
- ActiveSupport::Deprecation.warn(<<-WARNING.squish)
- Finding a last element by loading the relation when SQL ORDER
- can not be reversed is deprecated.
- Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
- Please call `to_a.last` if you still want to load the relation.
- WARNING
- find_last(limit)
- end
-
- def find_last(limit)
- limit ? to_a.last(limit) : to_a.last
+ if limit
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].desc).limit(limit).reverse
+ else
+ to_a.last(limit)
+ end
+ else
+ find_last
+ end
end
# Same as #last but raises ActiveRecord::RecordNotFound if no record
@@ -531,6 +523,19 @@ module ActiveRecord
relation.limit(limit).to_a
end
+ def find_last
+ if loaded?
+ @records.last
+ else
+ @last ||=
+ if limit_value
+ to_a.last
+ else
+ reverse_order.limit(1).to_a.first
+ end
+ end
+ end
+
private
def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index cbeb37dd22..75a74c052d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -506,7 +506,7 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_take_and_first_and_last_with_integer_should_use_sql
+ def test_take_and_first_and_last_with_integer_should_use_sql_limit
assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries }
assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries }
assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries }
@@ -516,30 +516,16 @@ class FinderTest < ActiveRecord::TestCase
assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2)
end
- def test_last_with_integer_and_order_should_use_sql
- relation = Topic.order("title")
- assert_queries(1) { relation.last(5) }
- assert !relation.loaded?
+ def test_last_with_integer_and_order_should_not_use_sql_limit
+ query = assert_sql { Topic.order("title").last(5).entries }
+ assert_equal 1, query.length
+ assert_no_match(/LIMIT/, query.first)
end
- def test_last_with_integer_and_reorder_should_use_sql
- relation = Topic.reorder("title")
- assert_queries(1) { relation.last(5) }
- assert !relation.loaded?
- end
-
- def test_last_on_loaded_relation_should_not_use_sql
- relation = Topic.limit(10).load
- assert_no_queries do
- relation.last
- relation.last(2)
- end
- end
-
- def test_last_with_irreversible_order
- assert_deprecated do
- Topic.order("coalesce(author_name, title)").last
- end
+ def test_last_with_integer_and_reorder_should_not_use_sql_limit
+ query = assert_sql { Topic.reorder("title").last(5).entries }
+ assert_equal 1, query.length
+ assert_no_match(/LIMIT/, query.first)
end
def test_take_and_first_and_last_with_integer_should_return_an_array
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index ca48164c54..8e4ca272ba 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -48,17 +48,11 @@ module ActiveSupport
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
synchronize do
unless @exclusive_thread == Thread.current
- if busy?(purpose)
+ if busy_for_exclusive?(purpose)
return false if no_wait
- loose_shares = @sharing.delete(Thread.current)
- @waiting[Thread.current] = compatible if loose_shares
-
- begin
- @cv.wait_while { busy?(purpose) }
- ensure
- @waiting.delete Thread.current
- @sharing[Thread.current] = loose_shares if loose_shares
+ yield_shares(purpose, compatible) do
+ @cv.wait_while { busy_for_exclusive?(purpose) }
end
end
@exclusive_thread = Thread.current
@@ -71,22 +65,26 @@ module ActiveSupport
# Relinquish the exclusive lock. Must only be called by the thread
# that called start_exclusive (and currently holds the lock).
- def stop_exclusive
+ def stop_exclusive(compatible: [])
synchronize do
raise "invalid unlock" if @exclusive_thread != Thread.current
@exclusive_depth -= 1
if @exclusive_depth == 0
@exclusive_thread = nil
- @cv.broadcast
+
+ yield_shares(nil, compatible) do
+ @cv.broadcast
+ @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ end
end
end
end
- def start_sharing
+ def start_sharing(purpose: :share)
synchronize do
- if @exclusive_thread && @exclusive_thread != Thread.current
- @cv.wait_while { @exclusive_thread }
+ if @sharing[Thread.current] == 0 && @exclusive_thread != Thread.current && busy_for_sharing?(purpose)
+ @cv.wait_while { busy_for_sharing?(purpose) }
end
@sharing[Thread.current] += 1
end
@@ -109,12 +107,12 @@ module ActiveSupport
# the block.
#
# See +start_exclusive+ for other options.
- def exclusive(purpose: nil, compatible: [], no_wait: false)
+ def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
begin
yield
ensure
- stop_exclusive
+ stop_exclusive(compatible: after_compatible)
end
end
end
@@ -132,11 +130,31 @@ module ActiveSupport
private
# Must be called within synchronize
- def busy?(purpose)
- (@exclusive_thread && @exclusive_thread != Thread.current) ||
- @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ def busy_for_exclusive?(purpose)
+ busy_for_sharing?(purpose) ||
@sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
end
+
+ def busy_for_sharing?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
+ end
+
+ def eligible_waiters?(compatible)
+ @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
+ end
+
+ def yield_shares(purpose, compatible)
+ loose_shares = @sharing.delete(Thread.current)
+ @waiting[Thread.current] = [purpose, compatible] if loose_shares
+
+ begin
+ yield
+ ensure
+ @waiting.delete Thread.current
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index fbeb904684..b6a1b25eee 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -8,13 +8,13 @@ module ActiveSupport #:nodoc:
end
def loading
- @lock.exclusive(purpose: :load, compatible: [:load]) do
+ @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do
yield
end
end
def unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do
yield
end
end
@@ -24,7 +24,7 @@ module ActiveSupport #:nodoc:
# concurrent activity, return immediately (without executing the
# block) instead of waiting.
def attempt_unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do
yield
end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index 465a657308..12953d99a6 100644
--- a/activesupport/test/share_lock_test.rb
+++ b/activesupport/test/share_lock_test.rb
@@ -114,14 +114,17 @@ class ShareLockTest < ActiveSupport::TestCase
[true, false].each do |use_upgrading|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
begin
+ together = Concurrent::CyclicBarrier.new(2)
conflicting_exclusive_threads = [
Thread.new do
@lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
@lock.exclusive(purpose: :red, compatible: [:green, :purple]) {}
end
end,
Thread.new do
@lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
@lock.exclusive(purpose: :blue, compatible: [:green]) {}
end
end
@@ -183,11 +186,14 @@ class ShareLockTest < ActiveSupport::TestCase
load_params = [:load, [:load]]
unload_params = [:unload, [:unload, :load]]
+ all_sharing = Concurrent::CyclicBarrier.new(4)
+
[load_params, load_params, unload_params, unload_params].permutation do |thread_params|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
threads = thread_params.map do |purpose, compatible|
Thread.new do
@lock.sharing do
+ all_sharing.wait
@lock.exclusive(purpose: purpose, compatible: compatible) do
scratch_pad_mutex.synchronize { scratch_pad << purpose }
end
@@ -209,6 +215,78 @@ class ShareLockTest < ActiveSupport::TestCase
end
end
+ def test_new_share_attempts_block_on_waiting_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ release_exclusive = Concurrent::CountDownLatch.new
+
+ waiting_exclusive = Thread.new do
+ @lock.sharing do
+ @lock.exclusive do
+ release_exclusive.wait
+ end
+ end
+ end
+ assert_threads_stuck waiting_exclusive
+
+ late_share_attempt = Thread.new do
+ @lock.sharing {}
+ end
+ assert_threads_stuck late_share_attempt
+
+ sharing_thread_release_latch.count_down
+ assert_threads_stuck late_share_attempt
+
+ release_exclusive.count_down
+ assert_threads_not_stuck late_share_attempt
+ end
+ end
+
+ def test_share_remains_reentrant_ignoring_a_waiting_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ ready = Concurrent::CyclicBarrier.new(2)
+ attempt_reentrancy = Concurrent::CountDownLatch.new
+
+ sharer = Thread.new do
+ @lock.sharing do
+ ready.wait
+ attempt_reentrancy.wait
+ @lock.sharing {}
+ end
+ end
+
+ exclusive = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck exclusive
+
+ attempt_reentrancy.count_down
+
+ assert_threads_not_stuck sharer
+ assert_threads_stuck exclusive
+ end
+ end
+
+ def test_compatible_exclusives_cooperate_to_both_proceed
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = 2.times.map do
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x, compatible: [:x], after_compatible: [:x]) {}
+ done.wait
+ end
+ end
+ end
+
+ assert_threads_not_stuck threads
+ end
+
def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads
scratch_pad = []
scratch_pad_mutex = Mutex.new
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 2650384df3..f45c8005da 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -253,6 +253,9 @@ Please refer to the [Changelog][action-pack] for detailed changes.
`ActionDispatch::IntegrationTest` instead.
([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d))
+* Rails will only generate "weak", instead of strong ETags.
+ ([Pull Request](https://github.com/rails/rails/pull/17573))
+
Action View
-------------
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index f02f6a18ee..0f98d12217 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -105,7 +105,7 @@ $ git checkout -b testing_branch
Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails.
```bash
-$ git remote add JohnSmith git://github.com/JohnSmith/rails.git
+$ git remote add JohnSmith https://github.com/JohnSmith/rails.git
$ git pull JohnSmith orange
```
@@ -204,7 +204,7 @@ In case you can't use the Rails development box, see [this other guide](developm
To be able to contribute code, you need to clone the Rails repository:
```bash
-$ git clone git://github.com/rails/rails.git
+$ git clone https://github.com/rails/rails.git
```
and create a dedicated branch:
@@ -506,7 +506,7 @@ Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and pr
Add the new remote to your local repository on your local machine:
```bash
-$ git remote add mine git@github.com:<your user name>/rails.git
+$ git remote add mine https://github.com:<your user name>/rails.git
```
Push to your remote:
@@ -520,7 +520,7 @@ You might have cloned your forked repository into your machine and might want to
In the directory you cloned your fork:
```bash
-$ git remote add rails git://github.com/rails/rails.git
+$ git remote add rails https://github.com/rails/rails.git
```
Download new commits and branches from the official repository:
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 5a745b10cd..d9e64d56ac 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1136,10 +1136,21 @@ For example, here's a small section of the `rails routes` output for a RESTful r
edit_user GET /users/:id/edit(.:format) users#edit
```
-You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable:
+You can search through your routes with the --grep option (-g for short). This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path.
-```bash
-$ CONTROLLER=users bin/rails routes
+```
+$ bin/rake routes --grep new_comment
+$ bin/rake routes -g POST
+$ bin/rake routes -g admin
+```
+
+If you only want to see the routes that map to a specific controller, there's the --controller option (-c for short).
+
+```
+$ bin/rake routes --controller users
+$ bin/rake routes --controller admin/users
+$ bin/rake routes -c Comments
+$ bin/rake routes -c Articles::CommentsController
```
TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap.
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 404e3c3e23..411cdbad19 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -86,7 +86,7 @@ module Rails
# added in the hook are taken into account.
initializer :set_clear_dependencies_hook, group: :all do
callback = lambda do
- ActiveSupport::Dependencies.interlock.attempt_unloading do
+ ActiveSupport::Dependencies.interlock.unloading do
ActiveSupport::DescendantsTracker.clear
ActiveSupport::Dependencies.clear
end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 1815c2fdc7..353b8b4e72 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,7 +1,35 @@
-desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/strip' # for strip_heredoc
+require 'optparse'
+
+desc 'Print out all defined routes in match order, with names. Target specific controller with --controller option - or its -c shorthand.'
task routes: :environment do
all_routes = Rails.application.routes.routes
require 'action_dispatch/routing/inspector'
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
- puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
+ if ARGV.any?{ |argv| argv.start_with? 'CONTROLLER' }
+ puts <<-eow.strip_heredoc
+ Passing `CONTROLLER` to `bin/rake routes` is deprecated and will be removed in Rails 5.1.
+ Please use `bin/rake routes -c controller_name` instead.
+ eow
+ end
+
+ routes_filter = nil
+ routes_filter = { controller: ENV['CONTROLLER'] } if ENV['CONTROLLER']
+
+ OptionParser.new do |opts|
+ opts.banner = "Usage: rake routes [options]"
+ opts.on("-c", "--controller [CONTROLLER]") do |controller|
+ routes_filter = { controller: controller }
+ end
+
+ opts.on("-g", "--grep [PATTERN]") do |pattern|
+ routes_filter = pattern
+ end
+
+ end.parse!(ARGV.reject { |x| x == "routes" })
+
+ puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter)
+
+ exit 0 # ensure extra arguments aren't interpreted as Rake tasks
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index b979ad64d1..7171aa6e1a 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -141,8 +141,67 @@ module ApplicationTests
end
RUBY
- ENV['CONTROLLER'] = 'cart'
- output = Dir.chdir(app_path){ `bin/rails routes` }
+ output = Dir.chdir(app_path){ `bin/rake routes CONTROLLER=cart` }
+ assert_equal ["Passing `CONTROLLER` to `bin/rake routes` is deprecated and will be removed in Rails 5.1.",
+ "Please use `bin/rake routes -c controller_name` instead.",
+ "Prefix Verb URI Pattern Controller#Action",
+ " cart GET /cart(.:format) cart#show\n"].join("\n"), output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c cart` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+ end
+
+ def test_rake_routes_with_namespaced_controller_environment
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ namespace :admin do
+ resource :post
+ end
+ end
+ RUBY
+ expected_output = [" Prefix Verb URI Pattern Controller#Action",
+ " admin_post POST /admin/post(.:format) admin/posts#create",
+ " new_admin_post GET /admin/post/new(.:format) admin/posts#new",
+ "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit",
+ " GET /admin/post(.:format) admin/posts#show",
+ " PATCH /admin/post(.:format) admin/posts#update",
+ " PUT /admin/post(.:format) admin/posts#update",
+ " DELETE /admin/post(.:format) admin/posts#destroy\n"].join("\n")
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c Admin::PostController` }
+ assert_equal expected_output, output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c PostController` }
+ assert_equal expected_output, output
+ end
+
+ def test_rake_routes_with_global_search_key
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/cart', to: 'cart#show'
+ get '/basketball', to: 'basketball#index'
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `bin/rake routes -g show` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+ end
+
+ def test_rake_routes_with_controller_search_key
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/cart', to: 'cart#show'
+ get '/basketball', to: 'basketball#index'
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `bin/rake routes -c cart` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+
+ output = Dir.chdir(app_path){ `bin/rake routes -c Cart` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+
+ output = Dir.chdir(app_path){ `bin/rake routes -c CartController` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end