Merge branch 'master' of github.com:rails/rails

Conflicts:
	guides/source/security.md
This commit is contained in:
Vijay Dev 2015-08-24 06:05:07 +00:00
commit 4f252cddc1
456 changed files with 7475 additions and 4943 deletions

@ -21,16 +21,12 @@ env:
- "GEM=aj:integration" - "GEM=aj:integration"
- "GEM=guides" - "GEM=guides"
rvm: rvm:
- 2.2.2 - 2.2.3
- ruby-head - ruby-head
- rbx-2
- jruby-head
matrix: matrix:
allow_failures: allow_failures:
- env: "GEM=ar:mysql" - env: "GEM=ar:mysql"
- rvm: ruby-head - rvm: ruby-head
- rvm: rbx-2
- rvm: jruby-head
fast_finish: true fast_finish: true
notifications: notifications:
email: false email: false

12
CODE_OF_CONDUCT.md Normal file

@ -0,0 +1,12 @@
# Contributor Code of Conduct
The Rails team is committed to fostering a welcoming community.
**Our Code of Conduct can be found here**:
http://rubyonrails.org/conduct/
For a history of updates, see the page history here:
https://github.com/rails/rails.github.com/commits/master/conduct/index.html

@ -7,6 +7,7 @@ gem 'rake', '>= 10.3'
# Active Job depends on the URI::GID::MissingModelIDError, which isn't released yet. # Active Job depends on the URI::GID::MissingModelIDError, which isn't released yet.
gem 'globalid', github: 'rails/globalid' gem 'globalid', github: 'rails/globalid'
gem 'rack', github: 'rack/rack'
# This needs to be with require false as it is # This needs to be with require false as it is
# loaded after loading the test library to # loaded after loading the test library to
@ -20,8 +21,9 @@ gem 'turbolinks'
gem 'arel', github: 'rails/arel', branch: 'master' gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail' gem 'mail', github: 'mikel/mail'
gem 'sprockets', '~> 3.0.0.rc.1' gem 'sprockets', github: 'rails/sprockets', branch: 'master'
gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master' gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'
gem 'sass-rails', github: 'rails/sass-rails', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used. # require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework) # This is to avoid ActiveModel (and by extension the entire framework)
@ -31,6 +33,7 @@ gem 'bcrypt', '~> 3.1.10', require: false
# This needs to be with require false to avoid # This needs to be with require false to avoid
# it being automatically loaded by sprockets # it being automatically loaded by sprockets
gem 'uglifier', '>= 1.3.0', require: false gem 'uglifier', '>= 1.3.0', require: false
gem 'sass', '>= 3.3', require: false
group :doc do group :doc do
gem 'sdoc', '~> 0.4.0' gem 'sdoc', '~> 0.4.0'
@ -49,7 +52,7 @@ group :job do
gem 'sidekiq', require: false gem 'sidekiq', require: false
gem 'sucker_punch', require: false gem 'sucker_punch', require: false
gem 'delayed_job', require: false gem 'delayed_job', require: false
gem 'queue_classic', require: false, platforms: :ruby gem 'queue_classic', github: "QueueClassic/queue_classic", require: false, platforms: :ruby
gem 'sneakers', require: false gem 'sneakers', require: false
gem 'que', require: false gem 'que', require: false
gem 'backburner', require: false gem 'backburner', require: false

@ -1,3 +1,10 @@
GIT
remote: git://github.com/QueueClassic/queue_classic.git
revision: d144db29f1436e9e8b3c7a1a1ecd4442316a9ecd
specs:
queue_classic (3.2.0.alpha)
pg (>= 0.17, < 0.19)
GIT GIT
remote: git://github.com/bkeepers/qu.git remote: git://github.com/bkeepers/qu.git
revision: d098e2657c92e89a6413bebd9c033930759c061f revision: d098e2657c92e89a6413bebd9c033930759c061f
@ -13,44 +20,70 @@ GIT
GIT GIT
remote: git://github.com/mikel/mail.git remote: git://github.com/mikel/mail.git
revision: b159e0a542962fdd5e292a48cfffa560d7cf412e revision: 64ef1a12efcdda53fd63e1456c2c564044bf82ce
specs: specs:
mail (2.6.3.edge) mail (2.6.3.edge)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
GIT
remote: git://github.com/rack/rack.git
revision: c94e22401d4719b4d78378c7b63362cd692f9005
specs:
rack (2.0.0.alpha)
json
GIT GIT
remote: git://github.com/rails/arel.git remote: git://github.com/rails/arel.git
revision: aac9da257f291ad8d2d4f914528881c240848bb2 revision: d5432b4616ff43fbb14540d351eed351e21bb20e
branch: master branch: master
specs: specs:
arel (7.0.0.alpha) arel (7.0.0.alpha)
GIT GIT
remote: git://github.com/rails/globalid.git remote: git://github.com/rails/globalid.git
revision: 4df66fb9e9f0c832d29119aa8bc30be55a614b71 revision: 8178ff2dc898a8f49dd71f6eb46f2a09712462de
specs: specs:
globalid (0.3.5) globalid (0.3.6)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
GIT GIT
remote: git://github.com/rails/jquery-rails.git remote: git://github.com/rails/jquery-rails.git
revision: 272abdd319bb3381b23182b928b25320590096b0 revision: 38053f45402f1ccc4cce5dd8c7005ec707376db3
branch: master branch: master
specs: specs:
jquery-rails (4.0.3) jquery-rails (4.0.4)
rails-dom-testing (~> 1.0) rails-dom-testing (~> 1.0)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
GIT GIT
remote: git://github.com/rails/sprockets-rails.git remote: git://github.com/rails/sass-rails.git
revision: 85b89c44ad40af3056899808475e6e4bf65c1f5a revision: 805cb17722b8a13ff00dffe20283a6ba2c9a45dc
branch: master branch: master
specs: specs:
sprockets-rails (3.0.0.beta1) sass-rails (6.0.0)
railties (>= 4.0.0, < 5.0)
sass (~> 3.3)
sprockets (>= 4.0)
sprockets-rails (< 4.0)
GIT
remote: git://github.com/rails/sprockets-rails.git
revision: e65e088575be4f6b994823b80a277affb0a57a5e
branch: master
specs:
sprockets-rails (3.0.0.beta2)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0, < 4.0) sprockets (>= 3.0.0)
GIT
remote: git://github.com/rails/sprockets.git
revision: a61550db7184fc83964fb983547ff09da9efa9be
branch: master
specs:
sprockets (4.0.0)
rack (~> 2.x)
PATH PATH
remote: . remote: .
@ -64,7 +97,7 @@ PATH
actionpack (5.0.0.alpha) actionpack (5.0.0.alpha)
actionview (= 5.0.0.alpha) actionview (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha) activesupport (= 5.0.0.alpha)
rack (~> 1.6) rack (~> 2.x)
rack-test (~> 0.6.3) rack-test (~> 0.6.3)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
@ -85,8 +118,10 @@ PATH
activesupport (= 5.0.0.alpha) activesupport (= 5.0.0.alpha)
arel (= 7.0.0.alpha) arel (= 7.0.0.alpha)
activesupport (5.0.0.alpha) activesupport (5.0.0.alpha)
concurrent-ruby (~> 0.9.0)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
method_source
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1) tzinfo (~> 1.1)
@ -111,83 +146,73 @@ PATH
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
amq-protocol (1.9.2) amq-protocol (2.0.0)
backburner (0.4.6) backburner (1.0.0)
beaneater (~> 0.3.1) beaneater (~> 1.0)
dante (~> 0.1.5) dante (> 0.1.5)
bcrypt (3.1.10) bcrypt (3.1.10)
bcrypt (3.1.10-x64-mingw32) bcrypt (3.1.10-x64-mingw32)
bcrypt (3.1.10-x86-mingw32) bcrypt (3.1.10-x86-mingw32)
beaneater (0.3.3) beaneater (1.0.0)
benchmark-ips (2.1.1) benchmark-ips (2.3.0)
builder (3.2.2) builder (3.2.2)
bunny (1.7.0) bunny (2.0.0)
amq-protocol (>= 1.9.2) amq-protocol (>= 1.9.2)
byebug (4.0.5) byebug (6.0.2)
columnize (= 0.9.0)
celluloid (0.16.0) celluloid (0.16.0)
timers (~> 4.0.0) timers (~> 4.0.0)
coffee-rails (4.1.0) coffee-rails (4.1.0)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
coffee-script (2.3.0) coffee-script (2.4.1)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.9.0) coffee-script-source (1.9.1.1)
columnize (0.9.0) concurrent-ruby (0.9.1)
connection_pool (2.1.1) connection_pool (2.2.0)
dalli (2.7.2) dalli (2.7.4)
dante (0.1.5) dante (0.2.0)
delayed_job (4.0.6) delayed_job (4.0.6)
activesupport (>= 3.0, < 5.0) activesupport (>= 3.0, < 5.0)
delayed_job_active_record (4.0.3) delayed_job_active_record (4.0.3)
activerecord (>= 3.0, < 5.0) activerecord (>= 3.0, < 5.0)
delayed_job (>= 3.0, < 4.1) delayed_job (>= 3.0, < 4.1)
erubis (2.7.0) erubis (2.7.0)
execjs (2.3.0) execjs (2.6.0)
hitimes (1.2.2) hitimes (1.2.2)
hitimes (1.2.2-x86-mingw32) hitimes (1.2.2-x86-mingw32)
i18n (0.7.0) i18n (0.7.0)
json (1.8.2) json (1.8.3)
kindlerb (0.1.1) kindlerb (0.1.1)
mustache mustache
nokogiri nokogiri
loofah (2.0.1) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
metaclass (0.0.4) metaclass (0.0.4)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.4.3) mime-types (2.6.1)
mini_portile (0.6.2) mini_portile (0.6.2)
minitest (5.3.3) minitest (5.3.3)
mocha (0.14.0) mocha (0.14.0)
metaclass (~> 0.0.1) metaclass (~> 0.0.1)
mono_logger (1.1.0) mono_logger (1.1.0)
multi_json (1.11.0) multi_json (1.11.2)
mustache (1.0.0) mustache (1.0.2)
mysql (2.9.1) mysql (2.9.1)
mysql2 (0.3.18) mysql2 (0.3.19)
nokogiri (1.6.6.2) nokogiri (1.6.6.2)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
nokogiri (1.6.6.2-x64-mingw32) pg (0.18.2)
mini_portile (~> 0.6.0) psych (2.0.15)
nokogiri (1.6.6.2-x86-mingw32) que (0.10.0)
mini_portile (~> 0.6.0)
pg (0.18.1)
psych (2.0.13)
que (0.9.2)
queue_classic (3.1.0)
pg (>= 0.17, < 0.19)
racc (1.4.12) racc (1.4.12)
rack (1.6.0)
rack-cache (1.2) rack-cache (1.2)
rack (>= 0.4) rack (>= 0.4)
rack-protection (1.5.3)
rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.6) rails-dom-testing (1.0.7)
activesupport (>= 4.2.0.beta, < 5.0) activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0) nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
@ -197,7 +222,7 @@ GEM
rdoc (4.2.0) rdoc (4.2.0)
redcarpet (3.2.3) redcarpet (3.2.3)
redis (3.2.1) redis (3.2.1)
redis-namespace (1.5.1) redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4) redis (~> 3.0, >= 3.0.4)
resque (1.25.2) resque (1.25.2)
mono_logger (~> 1.0) mono_logger (~> 1.0)
@ -210,47 +235,42 @@ GEM
redis (~> 3.0) redis (~> 3.0)
resque (~> 1.25) resque (~> 1.25)
rufus-scheduler (~> 3.0) rufus-scheduler (~> 3.0)
rufus-scheduler (3.0.9) rufus-scheduler (3.1.3)
tzinfo sass (3.4.17)
sdoc (0.4.1) sdoc (0.4.1)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0) rdoc (~> 4.0)
sequel (4.19.0) sequel (4.25.0)
serverengine (1.5.10) serverengine (1.5.10)
sigdump (~> 0.2.2) sigdump (~> 0.2.2)
sidekiq (3.3.2) sidekiq (3.4.2)
celluloid (>= 0.16.0) celluloid (~> 0.16.0)
connection_pool (>= 2.1.1) connection_pool (~> 2.2, >= 2.2.0)
json json (~> 1.0)
redis (>= 3.0.6) redis (~> 3.2, >= 3.2.1)
redis-namespace (>= 1.3.1) redis-namespace (~> 1.5, >= 1.5.2)
sigdump (0.2.2) sigdump (0.2.3)
sinatra (1.4.5) sinatra (1.0)
rack (~> 1.4) rack (>= 1.0)
rack-protection (~> 1.4) sneakers (1.1.1)
tilt (~> 1.3, >= 1.3.4) bunny (>= 1.7.0, <= 2.0.0)
sneakers (1.0.4)
bunny (~> 1.7.0)
serverengine (~> 1.5.5) serverengine (~> 1.5.5)
thor thor
thread (~> 0.1.7) thread (~> 0.1.7)
sprockets (3.0.2)
rack (~> 1.0)
sqlite3 (1.3.10) sqlite3 (1.3.10)
stackprof (0.2.7) stackprof (0.2.7)
sucker_punch (1.3.2) sucker_punch (1.5.1)
celluloid (~> 0.16.0) celluloid (= 0.16.0)
thor (0.19.1) thor (0.19.1)
thread (0.1.7) thread (0.1.7)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (1.4.1)
timers (4.0.1) timers (4.0.1)
hitimes hitimes
turbolinks (2.5.3) turbolinks (2.5.3)
coffee-rails coffee-rails
tzinfo (1.2.2) tzinfo (1.2.2)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (2.7.0) uglifier (2.7.1)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
vegas (0.1.11) vegas (0.1.11)
@ -292,19 +312,22 @@ DEPENDENCIES
qu-rails! qu-rails!
qu-redis qu-redis
que que
queue_classic queue_classic!
racc (>= 1.4.6) racc (>= 1.4.6)
rack!
rack-cache (~> 1.2) rack-cache (~> 1.2)
rails! rails!
rake (>= 10.3) rake (>= 10.3)
redcarpet (~> 3.2.3) redcarpet (~> 3.2.3)
resque resque
resque-scheduler resque-scheduler
sass (>= 3.3)
sass-rails!
sdoc (~> 0.4.0) sdoc (~> 0.4.0)
sequel sequel
sidekiq sidekiq
sneakers sneakers
sprockets (~> 3.0.0.rc.1) sprockets!
sprockets-rails! sprockets-rails!
sqlite3 (~> 1.3.6) sqlite3 (~> 1.3.6)
stackprof stackprof
@ -314,4 +337,4 @@ DEPENDENCIES
w3c_validators w3c_validators
BUNDLED WITH BUNDLED WITH
1.10.5 1.10.6

@ -76,6 +76,8 @@ and may also be used independently outside Rails.
We encourage you to contribute to Ruby on Rails! Please check out the We encourage you to contribute to Ruby on Rails! Please check out the
[Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org) [Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org)
Everyone interacting in Rails and its sub-projects codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/).
## Code Status ## Code Status
[![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails) [![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)

@ -1,43 +1,47 @@
= Releasing Rails # Releasing Rails
In this document, we'll cover the steps necessary to release Rails. Each In this document, we'll cover the steps necessary to release Rails. Each
section contains steps to take during that time before the release. The times section contains steps to take during that time before the release. The times
suggested in each header are just that: suggestions. However, they should suggested in each header are just that: suggestions. However, they should
really be considered as minimums. really be considered as minimums.
== 10 Days before release ## 10 Days before release
Today is mostly coordination tasks. Here are the things you must do today: Today is mostly coordination tasks. Here are the things you must do today:
=== Is the CI green? If not, make it green. (See "Fixing the CI") ### Is the CI green? If not, make it green. (See "Fixing the CI")
Do not release with a Red CI. You can find the CI status here: Do not release with a Red CI. You can find the CI status here:
http://travis-ci.org/rails/rails ```
http://travis-ci.org/rails/rails
```
=== Is Sam Ruby happy? If not, make him happy. ### Is Sam Ruby happy? If not, make him happy.
Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile
Web Development with Rails) all work. These are valuable integration tests Web Development with Rails) all work. These are valuable integration tests
for Rails. You can check the status of his tests here: for Rails. You can check the status of his tests here:
http://intertwingly.net/projects/dashboard.html ```
http://intertwingly.net/projects/dashboard.html
```
Do not release with Red AWDwR tests. Do not release with Red AWDwR tests.
=== Do we have any Git dependencies? If so, contact those authors. ### Do we have any Git dependencies? If so, contact those authors.
Having Git dependencies indicates that we depend on unreleased code. Having Git dependencies indicates that we depend on unreleased code.
Obviously Rails cannot be released when it depends on unreleased code. Obviously Rails cannot be released when it depends on unreleased code.
Contact the authors of those particular gems and work out a release date that Contact the authors of those particular gems and work out a release date that
suits them. suits them.
=== Contact the security team (either Koz or tenderlove) ### Contact the security team (either Koz or tenderlove)
Let them know of your plans to release. There may be security issues to be Let them know of your plans to release. There may be security issues to be
addressed, and that can impact your release date. addressed, and that can impact your release date.
=== Notify implementors. ### Notify implementors.
Ruby implementors have high stakes in making sure Rails works. Be kind and Ruby implementors have high stakes in making sure Rails works. Be kind and
give them a heads up that Rails will be released soonish. give them a heads up that Rails will be released soonish.
@ -54,27 +58,29 @@ lists:
Implementors will love you and help you. Implementors will love you and help you.
== 3 Days before release ### 3 Days before release
This is when you should release the release candidate. Here are your tasks This is when you should release the release candidate. Here are your tasks
for today: for today:
=== Is the CI green? If not, make it green. ### Is the CI green? If not, make it green.
=== Is Sam Ruby happy? If not, make him happy. ### Is Sam Ruby happy? If not, make him happy.
=== Contact the security team. CVE emails must be sent on this day. ### Contact the security team. CVE emails must be sent on this day.
=== Create a release branch. ### Create a release branch.
From the stable branch, create a release branch. For example, if you're From the stable branch, create a release branch. For example, if you're
releasing Rails 3.0.10, do this: releasing Rails 3.0.10, do this:
[aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10 ```
Switched to a new branch '3-0-10' [aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
[aaron@higgins rails (3-0-10)]$ Switched to a new branch '3-0-10'
[aaron@higgins rails (3-0-10)]$
```
=== Update each CHANGELOG. ### Update each CHANGELOG.
Many times commits are made without the CHANGELOG being updated. You should Many times commits are made without the CHANGELOG being updated. You should
review the commits since the last release, and fill in any missing information review the commits since the last release, and fill in any missing information
@ -82,15 +88,17 @@ for each CHANGELOG.
You can review the commits for the 3.0.10 release like this: You can review the commits for the 3.0.10 release like this:
[aaron@higgins rails (3-0-10)]$ git log v3.0.9.. ```
[aaron@higgins rails (3-0-10)]$ git log v3.0.9..
```
If you're doing a stable branch release, you should also ensure that all of If you're doing a stable branch release, you should also ensure that all of
the CHANGELOG entries in the stable branch are also synced to the master the CHANGELOG entries in the stable branch are also synced to the master
branch. branch.
=== Update the RAILS_VERSION file to include the RC. ### Update the RAILS_VERSION file to include the RC.
=== Build and test the gem. ### Build and test the gem.
Run `rake install` to generate the gems and install them locally. Then try Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes. generating a new app and ensure that nothing explodes.
@ -98,7 +106,7 @@ generating a new app and ensure that nothing explodes.
This will stop you from looking silly when you push an RC to rubygems.org and This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken. then realize it is broken.
=== Release the gem. ### Release the gem.
IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
to use Ruby 1.8 when releasing. to use Ruby 1.8 when releasing.
@ -108,14 +116,16 @@ RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
Here are the commands that `rake release` should use, so you can understand Here are the commands that `rake release` should use, so you can understand
what to do in case anything goes wrong: what to do in case anything goes wrong:
$ rake all:build ```
$ git commit -am'updating RAILS_VERSION' $ rake all:build
$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1 $ git commit -am'updating RAILS_VERSION'
$ git push $ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
$ git push --tags $ git push
$ for i in $(ls pkg); do gem push $i; done $ git push --tags
$ for i in $(ls pkg); do gem push $i; done
```
=== Send Rails release announcements ### Send Rails release announcements
Write a release announcement that includes the version, changes, and links to Write a release announcement that includes the version, changes, and links to
GitHub where people can find the specific commit list. Here are the mailing GitHub where people can find the specific commit list. Here are the mailing
@ -132,16 +142,16 @@ IMPORTANT: If any users experience regressions when using the release
candidate, you *must* postpone the release. Bugfix releases *should not* candidate, you *must* postpone the release. Bugfix releases *should not*
break existing applications. break existing applications.
=== Post the announcement to the Rails blog. ### Post the announcement to the Rails blog.
If you used Markdown format for your email, you can just paste it in to the If you used Markdown format for your email, you can just paste it in to the
blog. blog.
* http://weblog.rubyonrails.org * http://weblog.rubyonrails.org
=== Post the announcement to the Rails Twitter account. ### Post the announcement to the Rails Twitter account.
== Time between release candidate and actual release ## Time between release candidate and actual release
Check the rails-core mailing list and the GitHub issue list for regressions in Check the rails-core mailing list and the GitHub issue list for regressions in
the RC. the RC.
@ -155,7 +165,7 @@ When you fix the regressions, do not create a new branch. Fix them on the
stable branch, then cherry pick the commit to your release branch. No other stable branch, then cherry pick the commit to your release branch. No other
commits should be added to the release branch besides regression fixing commits. commits should be added to the release branch besides regression fixing commits.
== Day of release ## Day of release
Many of these steps are the same as for the release candidate, so if you need Many of these steps are the same as for the release candidate, so if you need
more explanation on a particular step, see the RC steps. more explanation on a particular step, see the RC steps.
@ -173,7 +183,7 @@ Today, do this stuff in this order:
* Email security lists * Email security lists
* Email general announcement lists * Email general announcement lists
=== Emailing the Rails security announce list ### Emailing the Rails security announce list
Email the security announce list once for each vulnerability fixed. Email the security announce list once for each vulnerability fixed.
@ -193,13 +203,13 @@ so we need to give them the security fixes in patch form.
* Merge the release branch to the stable branch. * Merge the release branch to the stable branch.
* Drink beer (or other cocktail) * Drink beer (or other cocktail)
== Misc ## Misc
=== Fixing the CI ### Fixing the CI
There are two simple steps for fixing the CI: There are two simple steps for fixing the CI:
1. Identify the problem 1. Identify the problem
2. Fix it 2. Fix it
Repeat these steps until the CI is green. Repeat these steps until the CI is green.

@ -820,7 +820,7 @@ def mail(headers = {}, &block)
# Set configure delivery behavior # Set configure delivery behavior
wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options)) wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options))
# Assign all headers except parts_order, content_type and body # Assign all headers except parts_order, content_type, body, template_name, and template_path
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path) assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
assignable.each { |k, v| m[k] = v } assignable.each { |k, v| m[k] = v }

@ -9,6 +9,7 @@
end end
require 'active_support/testing/autorun' require 'active_support/testing/autorun'
require 'active_support/testing/method_call_assertions'
require 'action_mailer' require 'action_mailer'
require 'action_mailer/test_case' require 'action_mailer/test_case'
@ -40,4 +41,6 @@ def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION) skip message if defined?(JRUBY_VERSION)
end end
require 'mocha/setup' # FIXME: stop using mocha class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
end

@ -505,9 +505,10 @@ def welcome
end end
test "calling deliver on the action should deliver the mail object" do test "calling deliver on the action should deliver the mail object" do
BaseMailer.expects(:deliver_mail).once assert_called(BaseMailer, :deliver_mail) do
mail = BaseMailer.welcome.deliver_now mail = BaseMailer.welcome.deliver_now
assert_equal 'The first email on new API!', mail.subject assert_equal 'The first email on new API!', mail.subject
end
end end
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
@ -517,9 +518,11 @@ def welcome
test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do
mail = Mail::Message.new mail = Mail::Message.new
mail.expects(:do_delivery).once assert_called(mail, :do_delivery) do
BaseMailer.expects(:welcome).returns(mail) assert_called(BaseMailer, :welcome, returns: mail) do
BaseMailer.welcome.deliver BaseMailer.welcome.deliver
end
end
end end
# Rendering # Rendering
@ -607,8 +610,9 @@ def self.delivered_email(mail)
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_observer(MyObserver) ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail) assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end end
end end
@ -616,8 +620,9 @@ def self.delivered_email(mail)
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_observer("BaseTest::MyObserver") ActionMailer::Base.register_observer("BaseTest::MyObserver")
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail) assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end end
end end
@ -625,8 +630,9 @@ def self.delivered_email(mail)
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_observer(:"base_test/my_observer") ActionMailer::Base.register_observer(:"base_test/my_observer")
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail) assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end end
end end
@ -634,9 +640,11 @@ def self.delivered_email(mail)
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver) ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail) assert_called_with(MyObserver, :delivered_email, [mail]) do
MySecondObserver.expects(:delivered_email).with(mail) assert_called_with(MySecondObserver, :delivered_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end
end end
end end
@ -654,8 +662,9 @@ def self.previewing_email(mail); end
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_interceptor(MyInterceptor) ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail) assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end end
end end
@ -663,8 +672,9 @@ def self.previewing_email(mail); end
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor") ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail) assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end end
end end
@ -672,8 +682,9 @@ def self.previewing_email(mail); end
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_interceptor(:"base_test/my_interceptor") ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail) assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end end
end end
@ -681,18 +692,21 @@ def self.previewing_email(mail); end
mail_side_effects do mail_side_effects do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor) ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail) assert_called_with(MyInterceptor, :delivering_email, [mail]) do
MySecondInterceptor.expects(:delivering_email).with(mail) assert_called_with(MySecondInterceptor, :delivering_email, [mail]) do
mail.deliver_now mail.deliver_now
end
end
end end
end end
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome['X-Proc-Method'] mail1 = ProcMailer.welcome['X-Proc-Method']
yesterday = 1.day.ago yesterday = 1.day.ago
Time.stubs(:now).returns(yesterday) Time.stub(:now, yesterday) do
mail2 = ProcMailer.welcome['X-Proc-Method'] mail2 = ProcMailer.welcome['X-Proc-Method']
assert(mail1.to_s.to_i > mail2.to_s.to_i) assert(mail1.to_s.to_i > mail2.to_s.to_i)
end
end end
test 'default values which have to_proc (e.g. symbols) should not be considered procs' do test 'default values which have to_proc (e.g. symbols) should not be considered procs' do
@ -877,33 +891,50 @@ def self.previewing_email(mail); end
test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(MyInterceptor) ActionMailer::Base.register_preview_interceptor(MyInterceptor)
mail = BaseMailer.welcome mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) stub_any_instance(BaseMailerPreview) do |instance|
MyInterceptor.expects(:previewing_email).with(mail) instance.stub(:welcome, mail) do
BaseMailerPreview.call(:welcome) assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end end
test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor") ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
mail = BaseMailer.welcome mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) stub_any_instance(BaseMailerPreview) do |instance|
MyInterceptor.expects(:previewing_email).with(mail) instance.stub(:welcome, mail) do
BaseMailerPreview.call(:welcome) assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end end
test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor") ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
mail = BaseMailer.welcome mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) stub_any_instance(BaseMailerPreview) do |instance|
MyInterceptor.expects(:previewing_email).with(mail) instance.stub(:welcome, mail) do
BaseMailerPreview.call(:welcome) assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end end
test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor) ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) stub_any_instance(BaseMailerPreview) do |instance|
MyInterceptor.expects(:previewing_email).with(mail) instance.stub(:welcome, mail) do
MySecondInterceptor.expects(:previewing_email).with(mail) assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome) assert_called_with(MySecondInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end
end end
end end

@ -102,16 +102,21 @@ def welcome(hash={})
end end
test "ActionMailer should be told when Mail gets delivered" do test "ActionMailer should be told when Mail gets delivered" do
DeliveryMailer.expects(:deliver_mail).once DeliveryMailer.delivery_method = :test
DeliveryMailer.welcome.deliver_now assert_called(DeliveryMailer, :deliver_mail) do
DeliveryMailer.welcome.deliver_now
end
end end
test "delivery method can be customized per instance" do test "delivery method can be customized per instance" do
Mail::SMTP.any_instance.expects(:deliver!) stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
email = DeliveryMailer.welcome.deliver_now assert_called(instance, :deliver!) do
assert_instance_of Mail::SMTP, email.delivery_method email = DeliveryMailer.welcome.deliver_now
email = DeliveryMailer.welcome(delivery_method: :test).deliver_now assert_instance_of Mail::SMTP, email.delivery_method
assert_instance_of Mail::TestMailer, email.delivery_method email = DeliveryMailer.welcome(delivery_method: :test).deliver_now
assert_instance_of Mail::TestMailer, email.delivery_method
end
end
end end
test "delivery method can be customized in subclasses not changing the parent" do test "delivery method can be customized in subclasses not changing the parent" do
@ -176,8 +181,11 @@ def welcome(hash={})
old_perform_deliveries = DeliveryMailer.perform_deliveries old_perform_deliveries = DeliveryMailer.perform_deliveries
begin begin
DeliveryMailer.perform_deliveries = false DeliveryMailer.perform_deliveries = false
Mail::Message.any_instance.expects(:deliver!).never stub_any_instance(Mail::Message) do |instance|
DeliveryMailer.welcome.deliver_now assert_not_called(instance, :deliver!) do
DeliveryMailer.welcome.deliver_now
end
end
ensure ensure
DeliveryMailer.perform_deliveries = old_perform_deliveries DeliveryMailer.perform_deliveries = old_perform_deliveries
end end

@ -1,6 +1,7 @@
require 'abstract_unit' require 'abstract_unit'
require 'action_view' require 'action_view'
require 'action_controller' require 'action_controller'
require 'active_support/deprecation'
class I18nTestMailer < ActionMailer::Base class I18nTestMailer < ActionMailer::Base
configure do |c| configure do |c|
@ -52,10 +53,15 @@ def app
end end
def test_send_mail def test_send_mail
Mail::SMTP.any_instance.expects(:deliver!) stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
with_translation 'de', email_subject: '[Anmeldung] Willkommen' do assert_called(instance, :deliver!) do
get '/test/send_mail' with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body ActiveSupport::Deprecation.silence do
get '/test/send_mail'
end
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
end end
end end

@ -1,3 +1,45 @@
* Using strings or symbols for middleware class names is deprecated. Convert
things like this:
middleware.use "Foo::Bar"
to this:
middleware.use Foo::Bar
* ActionController::TestSession now accepts a default value as well as
a block for generating a default value based off the key provided.
This fixes calls to session#fetch in ApplicationController instances that
take more two arguments or a block from raising `ArgumentError: wrong
number of arguments (2 for 1)` when performing controller tests.
*Matthew Gerrior*
* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
default block.
*Jonas Schuber Erlandsson*, *Roque Pinel*
* `ActionController::Parameters` no longer inherits from
`HashWithIndifferentAccess`
Inheriting from `HashWithIndifferentAccess` allowed users to call any
enumerable methods on `Parameters` object, resulting in a risk of losing the
`permitted?` status or even getting back a pure `Hash` object instead of
a `Parameters` object with proper sanitization.
By not inheriting from `HashWithIndifferentAccess`, we are able to make
sure that all methods that are defined in `Parameters` object will return
a proper `Parameters` object with a correct `permitted?` flag.
*Prem Sichanugrist*
* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
from the concurrent-ruby gem.
*Jerry D'Antonio*
* Add ability to filter parameters based on parent keys. * Add ability to filter parameters based on parent keys.
# matches {credit_card: {code: "xxxx"}} # matches {credit_card: {code: "xxxx"}}
@ -164,7 +206,8 @@
*arthurnn* *arthurnn*
* `ActionController#translate` supports symbols as shortcuts. * `ActionController#translate` supports symbols as shortcuts.
When shortcut is given it also lookups without action name. When a shortcut is given it also performs the lookup without the action
name.
*Max Melentiev* *Max Melentiev*

@ -21,7 +21,7 @@
s.add_dependency 'activesupport', version s.add_dependency 'activesupport', version
s.add_dependency 'rack', '~> 1.6' s.add_dependency 'rack', '~> 2.x'
s.add_dependency 'rack-test', '~> 0.6.3' s.add_dependency 'rack-test', '~> 0.6.3'
s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2' s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2'
s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'

@ -96,7 +96,7 @@ def clear_action_methods!
# ==== Returns # ==== Returns
# * <tt>String</tt> # * <tt>String</tt>
def controller_path def controller_path
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
end end
# Refresh the cached action_methods when a new action_method is added. # Refresh the cached action_methods when a new action_method is added.

@ -181,7 +181,7 @@ def add_template_helper(mod)
end end
def default_helper_module! def default_helper_module!
module_name = name.sub(/Controller$/, '') module_name = name.sub(/Controller$/, ''.freeze)
module_path = module_name.underscore module_path = module_name.underscore
helper module_path helper module_path
rescue LoadError => e rescue LoadError => e

@ -54,11 +54,11 @@ def rendered_format
Mime::TEXT Mime::TEXT
end end
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %w( DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
@_action_name @_response_body @_formats @_prefixes @_config @_action_name @_response_body @_formats @_prefixes @_config
@_view_context_class @_view_renderer @_lookup_context @_view_context_class @_view_renderer @_lookup_context
@_routes @_db_runtime @_routes @_db_runtime
).map(&:to_sym) )
# This method should return a hash with assigns. # This method should return a hash with assigns.
# You can overwrite this configuration per controller. # You can overwrite this configuration per controller.

@ -90,7 +90,7 @@ class API < Metal
# Shortcut helper that returns all the ActionController::API modules except # Shortcut helper that returns all the ActionController::API modules except
# the ones passed as arguments: # the ones passed as arguments:
# #
# class MetalController # class MyAPIBaseController < ActionController::Metal
# ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left| # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
# include left # include left
# end # end

@ -183,7 +183,7 @@ class Base < Metal
# Shortcut helper that returns all the modules included in # Shortcut helper that returns all the modules included in
# ActionController::Base except the ones passed as arguments: # ActionController::Base except the ones passed as arguments:
# #
# class MetalController # class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left # include left
# end # end
@ -252,7 +252,7 @@ def self.without_modules(*modules)
# Define some internal variables that should not be propagated to the view. # Define some internal variables that should not be propagated to the view.
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
:@_status, :@_headers, :@_params, :@_env, :@_response, :@_request, :@_status, :@_headers, :@_params, :@_response, :@_request,
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
def _protected_ivars # :nodoc: def _protected_ivars # :nodoc:

@ -8,7 +8,7 @@ module ActionController
# #
# You can read more about each approach by clicking the modules below. # You can read more about each approach by clicking the modules below.
# #
# Note: To turn off all caching, set # Note: To turn off all caching provided by Action Controller, set
# config.action_controller.perform_caching = false # config.action_controller.perform_caching = false
# #
# == \Caching stores # == \Caching stores

@ -25,7 +25,7 @@ def process_action(event)
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
message << " (#{additions.join(" | ")})" unless additions.blank? message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
message message
end end
end end

@ -1,5 +1,6 @@
require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack' require 'action_dispatch/middleware/stack'
require 'active_support/deprecation'
module ActionController module ActionController
# Extend ActionDispatch middleware stack to make it aware of options # Extend ActionDispatch middleware stack to make it aware of options
@ -11,22 +12,14 @@ module ActionController
# #
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc: class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc: class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
def initialize(klass, *args, &block) def initialize(klass, args, actions, strategy, block)
options = args.extract_options! @actions = actions
@only = Array(options.delete(:only)).map(&:to_s) @strategy = strategy
@except = Array(options.delete(:except)).map(&:to_s) super(klass, args, block)
args << options unless options.empty?
super
end end
def valid?(action) def valid?(action)
if @only.present? @strategy.call @actions, action
@only.include?(action)
elsif @except.present?
!@except.include?(action)
else
true
end
end end
end end
@ -37,6 +30,32 @@ def build(action, app = Proc.new)
middleware.valid?(action) ? middleware.build(a) : a middleware.valid?(action) ? middleware.build(a) : a
end end
end end
private
INCLUDE = ->(list, action) { list.include? action }
EXCLUDE = ->(list, action) { !list.include? action }
NULL = ->(list, action) { true }
def build_middleware(klass, args, block)
options = args.extract_options!
only = Array(options.delete(:only)).map(&:to_s)
except = Array(options.delete(:except)).map(&:to_s)
args << options unless options.empty?
strategy = NULL
list = nil
if only.any?
strategy = INCLUDE
list = only
elsif except.any?
strategy = EXCLUDE
list = except
end
Middleware.new(get_class(klass), args, list, strategy, block)
end
end end
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
@ -98,11 +117,10 @@ def build(action, app = Proc.new)
class Metal < AbstractController::Base class Metal < AbstractController::Base
abstract! abstract!
attr_internal_writer :env
def env def env
@_env ||= {} @_request.env
end end
deprecate :env
# Returns the last part of the controller's name, underscored, without the ending # Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>. # <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
@ -197,8 +215,7 @@ def dispatch(name, request) #:nodoc:
def set_request!(request) #:nodoc: def set_request!(request) #:nodoc:
@_request = request @_request = request
@_env = request.env @_request.controller_instance = self
@_env['action_controller.instance'] = self
end end
def to_a #:nodoc: def to_a #:nodoc:
@ -232,13 +249,13 @@ def self.call(env)
end end
# Returns a Rack endpoint for the given action name. # Returns a Rack endpoint for the given action name.
def self.action(name, klass = ActionDispatch::Request) def self.action(name)
if middleware_stack.any? if middleware_stack.any?
middleware_stack.build(name) do |env| middleware_stack.build(name) do |env|
new.dispatch(name, klass.new(env)) new.dispatch(name, ActionDispatch::Request.new(env))
end end
else else
lambda { |env| new.dispatch(name, klass.new(env)) } lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) }
end end
end end
end end

@ -85,6 +85,10 @@ def initialize(path)
@to_path = path @to_path = path
end end
def body
File.binread(to_path)
end
# Stream the file's contents if Rack::Sendfile isn't present. # Stream the file's contents if Rack::Sendfile isn't present.
def each def each
File.open(to_path, 'rb') do |file| File.open(to_path, 'rb') do |file|
@ -126,7 +130,7 @@ def each
# See +send_file+ for more information on HTTP Content-* headers and caching. # See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc: def send_data(data, options = {}) #:doc:
send_file_headers! options send_file_headers! options
render options.slice(:status, :content_type).merge(:text => data) render options.slice(:status, :content_type).merge(body: data)
end end
private private

@ -55,10 +55,10 @@ module ClassMethods
# You can pass any of the following options to affect the before_action callback # You can pass any of the following options to affect the before_action callback
# * <tt>only</tt> - The callback should be run only for this action # * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action # * <tt>except</tt> - The callback should be run for all actions except this action
# * <tt>if</tt> - A symbol naming an instance method or a proc; the callback # * <tt>if</tt> - A symbol naming an instance method or a proc; the
# will be called only when it returns a true value. # callback will be called only when it returns a true value.
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback # * <tt>unless</tt> - A symbol naming an instance method or a proc; the
# will be called only when it returns a false value. # callback will be called only when it returns a false value.
def force_ssl(options = {}) def force_ssl(options = {})
action_options = options.slice(*ACTION_OPTIONS) action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS) redirect_options = options.except(*ACTION_OPTIONS)
@ -71,8 +71,8 @@ def force_ssl(options = {})
# Redirect the existing request to use the HTTPS protocol. # Redirect the existing request to use the HTTPS protocol.
# #
# ==== Parameters # ==== Parameters
# * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options # * <tt>host_or_options</tt> - Either a host name or any of the url &
# available to the <tt>force_ssl</tt> method. # redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil) def force_ssl_redirect(host_or_options = nil)
unless request.ssl? unless request.ssl?
options = { options = {

@ -73,7 +73,7 @@ def helper_attr(*attrs)
# Provides a proxy to access helpers methods from outside the view. # Provides a proxy to access helpers methods from outside the view.
def helpers def helpers
@helper_proxy ||= begin @helper_proxy ||= begin
proxy = ActionView::Base.new proxy = ActionView::Base.new
proxy.config = config.inheritable_copy proxy.config = config.inheritable_copy
proxy.extend(_helpers) proxy.extend(_helpers)
@ -100,7 +100,7 @@ def modules_for_helpers(args)
def all_helpers_from_path(path) def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path| helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
names.sort! names.sort!
end end
helpers.uniq! helpers.uniq!

@ -94,7 +94,7 @@ def authenticate(request, &login_procedure)
end end
def has_basic_credentials?(request) def has_basic_credentials?(request)
request.authorization.present? && (auth_scheme(request) == 'Basic') request.authorization.present? && (auth_scheme(request).downcase == 'basic')
end end
def user_name_and_password(request) def user_name_and_password(request)
@ -502,7 +502,7 @@ def encode_credentials(token, options = {})
def authentication_request(controller, realm, message = nil) def authentication_request(controller, realm, message = nil)
message ||= "HTTP Token: Access denied.\n" message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}") controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
controller.__send__ :render, :text => message, :status => :unauthorized controller.__send__ :render, plain: message, status: :unauthorized
end end
end end
end end

@ -156,16 +156,16 @@ module MimeResponds
# It works for both inline: # It works for both inline:
# #
# respond_to do |format| # respond_to do |format|
# format.html.any { render text: "any" } # format.html.any { render html: "any" }
# format.html.phone { render text: "phone" } # format.html.phone { render html: "phone" }
# end # end
# #
# and block syntax: # and block syntax:
# #
# respond_to do |format| # respond_to do |format|
# format.html do |variant| # format.html do |variant|
# variant.any(:tablet, :phablet){ render text: "any" } # variant.any(:tablet, :phablet){ render html: "any" }
# variant.phone { render text: "phone" } # variant.phone { render html: "phone" }
# end # end
# end # end
# #

@ -4,8 +4,8 @@
require 'action_dispatch/http/mime_type' require 'action_dispatch/http/mime_type'
module ActionController module ActionController
# Wraps the parameters hash into a nested hash. This will allow clients to submit # Wraps the parameters hash into a nested hash. This will allow clients to
# POST requests without having to specify any root elements. # submit requests without having to specify any root elements.
# #
# This functionality is enabled in +config/initializers/wrap_parameters.rb+ # This functionality is enabled in +config/initializers/wrap_parameters.rb+
# and can be customized. # and can be customized.
@ -14,7 +14,7 @@ module ActionController
# a non-empty array: # a non-empty array:
# #
# class UsersController < ApplicationController # class UsersController < ApplicationController
# wrap_parameters format: [:json, :xml] # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
# end # end
# #
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to # If you enable +ParamsWrapper+ for +:json+ format, instead of having to

@ -94,7 +94,7 @@ def self.add(key, &block)
# This method is the opposite of add method. # This method is the opposite of add method.
# #
# Usage: # To remove a csv renderer:
# #
# ActionController::Renderers.remove(:csv) # ActionController::Renderers.remove(:csv)
def self.remove(key) def self.remove(key)

@ -1,3 +1,6 @@
require 'active_support/deprecation'
require 'active_support/core_ext/string/filters'
module ActionController module ActionController
module Rendering module Rendering
extend ActiveSupport::Concern extend ActiveSupport::Concern
@ -74,6 +77,17 @@ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
def _normalize_options(options) #:nodoc: def _normalize_options(options) #:nodoc:
_normalize_text(options) _normalize_text(options)
if options[:text]
ActiveSupport::Deprecation.warn <<-WARNING.squish
`render :text` is deprecated because it does not actually render a
`text/plain` response. Switch to `render plain: 'plain text'` to
render as `text/plain`, `render html: '<strong>HTML</strong>'` to
render as `text/html`, or `render body: 'raw'` to match the deprecated
behavior and render with the default Content-Type, which is
`text/plain`.
WARNING
end
if options[:html] if options[:html]
options[:html] = ERB::Util.html_escape(options[:html]) options[:html] = ERB::Util.html_escape(options[:html])
end end

@ -20,7 +20,7 @@ class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
# Since HTML and JavaScript requests are typically made from the browser, we # Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can # need to ensure to verify request authenticity for the web browser. We can
# use session-oriented authentication for these types of requests, by using # use session-oriented authentication for these types of requests, by using
# the `protect_form_forgery` method in our controllers. # the `protect_from_forgery` method in our controllers.
# #
# GET requests are not protected since they don't have side effects like writing # GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are # to the database and don't leak sensitive information. JavaScript requests are
@ -136,17 +136,17 @@ def initialize(controller)
# This is the method that defines the application behavior when a request is found to be unverified. # This is the method that defines the application behavior when a request is found to be unverified.
def handle_unverified_request def handle_unverified_request
request = @controller.request request = @controller.request
request.session = NullSessionHash.new(request.env) request.session = NullSessionHash.new(request)
request.env['action_dispatch.request.flash_hash'] = nil request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true } request.env['rack.session.options'] = { skip: true }
request.env['action_dispatch.cookies'] = NullCookieJar.build(request) request.cookie_jar = NullCookieJar.build(request, {})
end end
protected protected
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
def initialize(env) def initialize(req)
super(nil, env) super(nil, req)
@data = {} @data = {}
@loaded = true @loaded = true
end end
@ -160,14 +160,6 @@ def exists?
end end
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc: class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
host = request.host
secure = request.ssl?
new(key_generator, host, secure, options_for_env({}))
end
def write(*) def write(*)
# nothing # nothing
end end

@ -199,7 +199,7 @@ module Streaming
def _process_options(options) #:nodoc: def _process_options(options) #:nodoc:
super super
if options[:stream] if options[:stream]
if env["HTTP_VERSION"] == "HTTP/1.0" if request.version == "HTTP/1.0"
options.delete(:stream) options.delete(:stream)
else else
headers["Cache-Control"] ||= "no-cache" headers["Cache-Control"] ||= "no-cache"

@ -104,10 +104,12 @@ def initialize(params) # :nodoc:
# params = ActionController::Parameters.new(key: 'value') # params = ActionController::Parameters.new(key: 'value')
# params[:key] # => "value" # params[:key] # => "value"
# params["key"] # => "value" # params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these # By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action' # params are present. The default includes both 'controller' and 'action'
# because they are added by Rails and should be of no concern. One way # because they are added by Rails and should be of no concern. One way
@ -144,11 +146,22 @@ def self.const_missing(const_name)
# params = ActionController::Parameters.new(name: 'Francesco') # params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => true # params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco"> # Person.new(params) # => #<Person id: nil, name: "Francesco">
def initialize(attributes = nil) def initialize(parameters = {})
super(attributes) @parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters @permitted = self.class.permit_all_parameters
end end
# Returns true if another +Parameters+ object contains the same content and
# permitted flag, or other Hash-like object contains the same content. This
# override is in place so you can perform a comparison with `Hash`.
def ==(other_hash)
if other_hash.respond_to?(:permitted?)
super
else
@parameters == other_hash
end
end
# Returns a safe +Hash+ representation of this parameter with all # Returns a safe +Hash+ representation of this parameter with all
# unpermitted keys removed. # unpermitted keys removed.
# #
@ -162,7 +175,7 @@ def initialize(attributes = nil)
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h def to_h
if permitted? if permitted?
to_hash @parameters.to_h
else else
slice(*self.class.always_permitted_parameters).permit!.to_h slice(*self.class.always_permitted_parameters).permit!.to_h
end end
@ -170,20 +183,17 @@ def to_h
# Returns an unsafe, unfiltered +Hash+ representation of this parameter. # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
def to_unsafe_h def to_unsafe_h
to_hash @parameters.to_h
end end
alias_method :to_unsafe_hash, :to_unsafe_h alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair like # Convert all hashes in values into parameters, then yield each pair like
# the same way as <tt>Hash#each_pair</tt> # the same way as <tt>Hash#each_pair</tt>
def each_pair(&block) def each_pair(&block)
super do |key, value| @parameters.each_pair do |key, value|
convert_hashes_to_parameters(key, value) yield key, convert_hashes_to_parameters(key, value)
end end
super
end end
alias_method :each, :each_pair alias_method :each, :each_pair
# Attribute that keeps track of converted arrays, if any, to avoid double # Attribute that keeps track of converted arrays, if any, to avoid double
@ -347,7 +357,13 @@ def permit(*filters)
# params[:person] # => {"name"=>"Francesco"} # params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil # params[:none] # => nil
def [](key) def [](key)
convert_hashes_to_parameters(key, super) convert_hashes_to_parameters(key, @parameters[key])
end
# Assigns a value to a given +key+. The given key may still get filtered out
# when +permit+ is called.
def []=(key, value)
@parameters[key] = value
end end
# Returns a parameter for the given +key+. If the +key+ # Returns a parameter for the given +key+. If the +key+
@ -361,10 +377,16 @@ def [](key)
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args) def fetch(key, *args, &block)
convert_hashes_to_parameters(key, super, false) convert_value_to_parameters(
rescue KeyError @parameters.fetch(key) {
raise ActionController::ParameterMissing.new(key) if block_given?
yield
else
args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
end
}
)
end end
# Returns a new <tt>ActionController::Parameters</tt> instance that # Returns a new <tt>ActionController::Parameters</tt> instance that
@ -375,7 +397,24 @@ def fetch(key, *args)
# params.slice(:a, :b) # => {"a"=>1, "b"=>2} # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {} # params.slice(:d) # => {}
def slice(*keys) def slice(*keys)
new_instance_with_inherited_permitted_status(super) new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
end
# Returns current <tt>ActionController::Parameters</tt> instance which
# contains only the given +keys+.
def slice!(*keys)
@parameters.slice!(*keys)
self
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
# filters out the given +keys+.
#
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.except(:a, :b) # => {"c"=>3}
# params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
def except(*keys)
new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end end
# Removes and returns the key/value pairs matching the given keys. # Removes and returns the key/value pairs matching the given keys.
@ -384,7 +423,7 @@ def slice(*keys)
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2} # params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
# params # => {"c"=>3} # params # => {"c"=>3}
def extract!(*keys) def extract!(*keys)
new_instance_with_inherited_permitted_status(super) new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end end
# Returns a new <tt>ActionController::Parameters</tt> with the results of # Returns a new <tt>ActionController::Parameters</tt> with the results of
@ -393,36 +432,80 @@ def extract!(*keys)
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3) # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 } # params.transform_values { |x| x * 2 }
# # => {"a"=>2, "b"=>4, "c"=>6} # # => {"a"=>2, "b"=>4, "c"=>6}
def transform_values def transform_values(&block)
if block_given? if block
new_instance_with_inherited_permitted_status(super) new_instance_with_inherited_permitted_status(
@parameters.transform_values(&block)
)
else else
super @parameters.transform_values
end end
end end
# This method is here only to make sure that the returned object has the # Performs values transformation and returns the altered
# correct +permitted+ status. It should not matter since the parent of # <tt>ActionController::Parameters</tt> instance.
# this object is +HashWithIndifferentAccess+ def transform_values!(&block)
def transform_keys # :nodoc: @parameters.transform_values!(&block)
if block_given? self
new_instance_with_inherited_permitted_status(super) end
# Returns a new <tt>ActionController::Parameters</tt> instance with the
# results of running +block+ once for every key. The values are unchanged.
def transform_keys(&block)
if block
new_instance_with_inherited_permitted_status(
@parameters.transform_keys(&block)
)
else else
super @parameters.transform_keys
end end
end end
# Performs keys transformation and returns the altered
# <tt>ActionController::Parameters</tt> instance.
def transform_keys!(&block)
@parameters.transform_keys!(&block)
self
end
# Deletes and returns a key-value pair from +Parameters+ whose key is equal # Deletes and returns a key-value pair from +Parameters+ whose key is equal
# to key. If the key is not found, returns the default value. If the # to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key # optional code block is given and the key is not found, pass in the key
# and return the result of block. # and return the result of block.
def delete(key, &block) def delete(key, &block)
convert_hashes_to_parameters(key, super, false) convert_value_to_parameters(@parameters.delete(key))
end
# Returns a new instance of <tt>ActionController::Parameters</tt> with only
# items that the block evaluates to true.
def select(&block)
new_instance_with_inherited_permitted_status(@parameters.select(&block))
end end
# Equivalent to Hash#keep_if, but returns nil if no changes were made. # Equivalent to Hash#keep_if, but returns nil if no changes were made.
def select!(&block) def select!(&block)
convert_value_to_parameters(super) @parameters.select!(&block)
self
end
alias_method :keep_if, :select!
# Returns a new instance of <tt>ActionController::Parameters</tt> with items
# that the block evaluates to true removed.
def reject(&block)
new_instance_with_inherited_permitted_status(@parameters.reject(&block))
end
# Removes items that the block evaluates to true and returns self.
def reject!(&block)
@parameters.reject!(&block)
self
end
alias_method :delete_if, :reject!
# Return values that were assigned to the given +keys+. Note that all the
# +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
def values_at(*keys)
convert_value_to_parameters(@parameters.values_at(*keys))
end end
# Returns an exact copy of the <tt>ActionController::Parameters</tt> # Returns an exact copy of the <tt>ActionController::Parameters</tt>
@ -439,11 +522,30 @@ def dup
end end
end end
# Returns a new <tt>ActionController::Parameters</tt> with all keys from
# +other_hash+ merges into current hash.
def merge(other_hash)
new_instance_with_inherited_permitted_status(
@parameters.merge(other_hash)
)
end
# This is required by ActiveModel attribute assignment, so that user can
# pass +Parameters+ to a mass assignment methods in a model. It should not
# matter as we are using +HashWithIndifferentAccess+ internally.
def stringify_keys # :nodoc:
dup
end
protected protected
def permitted=(new_permitted) def permitted=(new_permitted)
@permitted = new_permitted @permitted = new_permitted
end end
def fields_for_style?
@parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
end
private private
def new_instance_with_inherited_permitted_status(hash) def new_instance_with_inherited_permitted_status(hash)
self.class.new(hash).tap do |new_instance| self.class.new(hash).tap do |new_instance|
@ -451,40 +553,41 @@ def new_instance_with_inherited_permitted_status(hash)
end end
end end
def convert_hashes_to_parameters(key, value, assign_if_converted=true) def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value) converted = convert_value_to_parameters(value)
self[key] = converted if assign_if_converted && !converted.equal?(value) @parameters[key] = converted unless converted.equal?(value)
converted converted
end end
def convert_value_to_parameters(value) def convert_value_to_parameters(value)
if value.is_a?(Array) && !converted_arrays.member?(value) case value
when Array
return value if converted_arrays.member?(value)
converted = value.map { |_| convert_value_to_parameters(_) } converted = value.map { |_| convert_value_to_parameters(_) }
converted_arrays << converted converted_arrays << converted
converted converted
elsif value.is_a?(Parameters) || !value.is_a?(Hash) when Hash
value
else
self.class.new(value) self.class.new(value)
else
value
end end
end end
def each_element(object) def each_element(object)
if object.is_a?(Array) case object
object.map { |el| yield el }.compact when Array
elsif fields_for_style?(object) object.grep(Parameters).map { |el| yield el }.compact
hash = object.class.new when Parameters
object.each { |k,v| hash[k] = yield v } if object.fields_for_style?
hash hash = object.class.new
else object.each { |k,v| hash[k] = yield v }
yield object hash
else
yield object
end
end end
end end
def fields_for_style?(object)
object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
end
def unpermitted_parameters!(params) def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params) unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any? if unpermitted_keys.any?
@ -546,14 +649,8 @@ def permitted_scalar_filter(params, key)
end end
def array_of_permitted_scalars?(value) def array_of_permitted_scalars?(value)
if value.is_a?(Array) if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
value.all? {|element| permitted_scalar?(element)} yield value
end
end
def array_of_permitted_scalars_filter(params, key)
if has_key?(key) && array_of_permitted_scalars?(self[key])
params[key] = self[key]
end end
end end
@ -564,17 +661,17 @@ def hash_filter(params, filter)
# Slicing filters out non-declared keys. # Slicing filters out non-declared keys.
slice(*filter.keys).each do |key, value| slice(*filter.keys).each do |key, value|
next unless value next unless value
next unless has_key? key
if filter[key] == EMPTY_ARRAY if filter[key] == EMPTY_ARRAY
# Declaration { comment_ids: [] }. # Declaration { comment_ids: [] }.
array_of_permitted_scalars_filter(params, key) array_of_permitted_scalars?(self[key]) do |val|
params[key] = val
end
else else
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element| params[key] = each_element(value) do |element|
if element.is_a?(Hash) element.permit(*Array.wrap(filter[key]))
element = self.class.new(element) unless element.respond_to?(:permit)
element.permit(*Array.wrap(filter[key]))
end
end end
end end
end end

@ -41,7 +41,11 @@ def url_options
if original_script_name if original_script_name
options[:original_script_name] = original_script_name options[:original_script_name] = original_script_name
else else
options[:script_name] = same_origin ? request.script_name.dup : script_name if same_origin
options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
else
options[:script_name] = script_name
end
end end
options.freeze options.freeze
else else

@ -1,39 +0,0 @@
module ActionController
class Middleware < Metal
class ActionMiddleware
def initialize(controller, app)
@controller, @app = controller, app
end
def call(env)
request = ActionDispatch::Request.new(env)
@controller.build(@app).dispatch(:index, request)
end
end
class << self
alias build new
def new(app)
ActionMiddleware.new(self, app)
end
end
attr_internal :app
def process(action)
response = super
self.status, self.headers, self.response_body = response if response.is_a?(Array)
response
end
def initialize(app)
super()
@_app = app
end
def index
call(env)
end
end
end

@ -1,4 +1,5 @@
require 'rack/session/abstract/id' require 'rack/session/abstract/id'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/keys'
@ -35,21 +36,23 @@ def initialize(env, session)
end end
def query_string=(string) def query_string=(string)
@env[Rack::QUERY_STRING] = string set_header Rack::QUERY_STRING, string
end end
def request_parameters=(params) def request_parameters=(params)
@env["action_dispatch.request.request_parameters"] = params set_header "action_dispatch.request.request_parameters", params
end end
def assign_parameters(routes, controller_path, action, parameters = {}) def content_type=(type)
parameters = parameters.symbolize_keys set_header 'CONTENT_TYPE', type
generated_path, extra_keys = routes.generate_extras(parameters.merge(:controller => controller_path, :action => action)) end
def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
non_path_parameters = {} non_path_parameters = {}
path_parameters = {} path_parameters = {}
parameters.each do |key, value| parameters.each do |key, value|
if extra_keys.include?(key) || key == :action || key == :controller if query_string_keys.include?(key)
non_path_parameters[key] = value non_path_parameters[key] = value
else else
if value.is_a?(Array) if value.is_a?(Array)
@ -68,10 +71,12 @@ def assign_parameters(routes, controller_path, action, parameters = {})
end end
else else
if ENCODER.should_multipart?(non_path_parameters) if ENCODER.should_multipart?(non_path_parameters)
@env['CONTENT_TYPE'] = ENCODER.content_type self.content_type = ENCODER.content_type
data = ENCODER.build_multipart non_path_parameters data = ENCODER.build_multipart non_path_parameters
else else
@env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded' get_header('CONTENT_TYPE') do |k|
set_header k, 'application/x-www-form-urlencoded'
end
# FIXME: setting `request_parametes` is normally handled by the # FIXME: setting `request_parametes` is normally handled by the
# params parser middleware, and we should remove this roundtripping # params parser middleware, and we should remove this roundtripping
@ -93,8 +98,8 @@ def assign_parameters(routes, controller_path, action, parameters = {})
end end
end end
@env['CONTENT_LENGTH'] = data.length.to_s set_header 'CONTENT_LENGTH', data.length.to_s
@env['rack.input'] = StringIO.new(data) set_header 'rack.input', StringIO.new(data)
end end
@env["PATH_INFO"] ||= generated_path @env["PATH_INFO"] ||= generated_path
@ -132,23 +137,13 @@ def content_type
end.new end.new
end end
class TestResponse < ActionDispatch::TestResponse
end
class LiveTestResponse < Live::Response class LiveTestResponse < Live::Response
def body
@body ||= super
end
# Was the response successful? # Was the response successful?
alias_method :success?, :successful? alias_method :success?, :successful?
# Was the URL not found? # Was the URL not found?
alias_method :missing?, :not_found? alias_method :missing?, :not_found?
# Were we redirected?
alias_method :redirect?, :redirection?
# Was there a server-side error? # Was there a server-side error?
alias_method :error?, :server_error? alias_method :error?, :server_error?
end end
@ -181,6 +176,10 @@ def destroy
clear clear
end end
def fetch(*args, &block)
@data.fetch(*args, &block)
end
private private
def load! def load!
@ -237,7 +236,7 @@ def load!
# request. You can modify this object before sending the HTTP request. For example, # request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request. # you might want to set some session properties before sending a GET request.
# <b>@response</b>:: # <b>@response</b>::
# An ActionController::TestResponse object, representing the response # An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you # after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail. # may use this object to inspect the HTTP response in detail.
@ -457,7 +456,7 @@ def process(action, *args)
end end
if body.present? if body.present?
@request.env['RAW_POST_DATA'] = body @request.set_header 'RAW_POST_DATA', body
end end
if http_method.present? if http_method.present?
@ -479,44 +478,50 @@ def process(action, *args)
end end
self.cookies.update @request.cookies self.cookies.update @request.cookies
@request.env['HTTP_COOKIE'] = cookies.to_header @request.set_header 'HTTP_COOKIE', cookies.to_header
@request.env['action_dispatch.cookies'] = nil @request.delete_header 'action_dispatch.cookies'
@request = TestRequest.new scrub_env!(@request.env), @request.session @request = TestRequest.new scrub_env!(@request.env), @request.session
@response = build_response @response_klass @response = build_response @response_klass
@response.request = @request @response.request = @request
@controller.recycle! @controller.recycle!
@request.env['REQUEST_METHOD'] = http_method @request.set_header 'REQUEST_METHOD', http_method
controller_class_name = @controller.class.anonymous? ? parameters = parameters.symbolize_keys
"anonymous" :
@controller.class.controller_path
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
generated_path = generated_path(generated_extras)
query_string_keys = query_parameter_names(generated_extras)
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
@request.session.update(session) if session @request.session.update(session) if session
@request.flash.update(flash || {}) @request.flash.update(flash || {})
if xhr if xhr
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') @request.get_header('HTTP_ACCEPT') do |k|
@request.set_header k, [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
end
end end
@controller.request = @request @controller.request = @request
@controller.response = @response @controller.response = @response
@request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root @request.get_header("SCRIPT_NAME") do |k|
@request.set_header k, @controller.config.relative_url_root
end
@controller.recycle! @controller.recycle!
@controller.process(action) @controller.process(action)
@request.env.delete 'HTTP_COOKIE' @request.delete_header 'HTTP_COOKIE'
if cookies = @request.env['action_dispatch.cookies'] if @request.have_cookie_jar?
unless @response.committed? unless @response.committed?
cookies.write(@response) @request.cookie_jar.write(@response)
self.cookies.update(cookies.instance_variable_get(:@cookies)) self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
end end
end end
@response.prepare! @response.prepare!
@ -528,18 +533,30 @@ def process(action, *args)
end end
if xhr if xhr
@request.env.delete 'HTTP_X_REQUESTED_WITH' @request.delete_header 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT' @request.delete_header 'HTTP_ACCEPT'
end end
@request.query_string = '' @request.query_string = ''
@response @response
end end
def controller_class_name
@controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
end
def generated_path(generated_extras)
generated_extras[0]
end
def query_parameter_names(generated_extras)
generated_extras[1] + [:controller, :action]
end
def setup_controller_request_and_response def setup_controller_request_and_response
@controller = nil unless defined? @controller @controller = nil unless defined? @controller
@response_klass = TestResponse @response_klass = ActionDispatch::TestResponse
if klass = self.class.controller_class if klass = self.class.controller_class
if klass < ActionController::Live if klass < ActionController::Live

@ -50,13 +50,13 @@ def filtered_path
protected protected
def parameter_filter def parameter_filter
parameter_filter_for @env.fetch("action_dispatch.parameter_filter") { parameter_filter_for get_header("action_dispatch.parameter_filter") {
return NULL_PARAM_FILTER return NULL_PARAM_FILTER
} }
end end
def env_filter def env_filter
user_key = @env.fetch("action_dispatch.parameter_filter") { user_key = get_header("action_dispatch.parameter_filter") {
return NULL_ENV_FILTER return NULL_ENV_FILTER
} }
parameter_filter_for(Array(user_key) + ENV_MATCH) parameter_filter_for(Array(user_key) + ENV_MATCH)

@ -5,8 +5,7 @@ module FilterRedirect
FILTERED = '[FILTERED]'.freeze # :nodoc: FILTERED = '[FILTERED]'.freeze # :nodoc:
def filtered_location # :nodoc: def filtered_location # :nodoc:
filters = location_filter if location_filter_match?
if !filters.empty? && location_filter_match?(filters)
FILTERED FILTERED
else else
location location
@ -15,7 +14,7 @@ def filtered_location # :nodoc:
private private
def location_filter def location_filters
if request if request
request.env['action_dispatch.redirect_filter'] || [] request.env['action_dispatch.redirect_filter'] || []
else else
@ -23,12 +22,12 @@ def location_filter
end end
end end
def location_filter_match?(filters) def location_filter_match?
filters.any? do |filter| location_filters.any? do |filter|
if String === filter if String === filter
location.include?(filter) location.include?(filter)
elsif Regexp === filter elsif Regexp === filter
location.match(filter) location =~ filter
end end
end end
end end

@ -30,27 +30,32 @@ class Headers
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
include Enumerable include Enumerable
attr_reader :env
def initialize(env = {}) # :nodoc: def self.from_hash(hash)
@env = env new ActionDispatch::Request.new hash
end
def initialize(request) # :nodoc:
@req = request
end end
# Returns the value for the given key mapped to @env. # Returns the value for the given key mapped to @env.
def [](key) def [](key)
@env[env_name(key)] @req.get_header env_name(key)
end end
# Sets the given value for the key mapped to @env. # Sets the given value for the key mapped to @env.
def []=(key, value) def []=(key, value)
@env[env_name(key)] = value @req.set_header env_name(key), value
end end
def key?(key) def key?(key)
@env.key? env_name(key) @req.has_header? env_name(key)
end end
alias :include? :key? alias :include? :key?
DEFAULT = Object.new # :nodoc:
# Returns the value for the given key mapped to @env. # Returns the value for the given key mapped to @env.
# #
# If the key is not found and an optional code block is not provided, # If the key is not found and an optional code block is not provided,
@ -58,18 +63,22 @@ def key?(key)
# #
# If the code block is provided, then it will be run and # If the code block is provided, then it will be run and
# its result returned. # its result returned.
def fetch(key, *args, &block) def fetch(key, default = DEFAULT)
@env.fetch env_name(key), *args, &block @req.get_header(env_name(key)) do
return default unless default == DEFAULT
return yield if block_given?
raise NameError, key
end
end end
def each(&block) def each(&block)
@env.each(&block) @req.each_header(&block)
end end
# Returns a new Http::Headers instance containing the contents of # Returns a new Http::Headers instance containing the contents of
# <tt>headers_or_env</tt> and the original instance. # <tt>headers_or_env</tt> and the original instance.
def merge(headers_or_env) def merge(headers_or_env)
headers = Http::Headers.new(env.dup) headers = @req.dup.headers
headers.merge!(headers_or_env) headers.merge!(headers_or_env)
headers headers
end end
@ -79,11 +88,14 @@ def merge(headers_or_env)
# <tt>headers_or_env</tt>. # <tt>headers_or_env</tt>.
def merge!(headers_or_env) def merge!(headers_or_env)
headers_or_env.each do |key, value| headers_or_env.each do |key, value|
self[env_name(key)] = value @req.set_header env_name(key), value
end end
end end
def env; @req.env.dup; end
private private
# Converts a HTTP header name to an environment variable name if it is # Converts a HTTP header name to an environment variable name if it is
# not contained within the headers hash. # not contained within the headers hash.
def env_name(key) def env_name(key)

@ -15,12 +15,13 @@ module MimeNegotiation
# For backward compatibility, the post \format is extracted from the # For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present. # X-Post-Data-Format HTTP header if present.
def content_mime_type def content_mime_type
@env["action_dispatch.request.content_type"] ||= begin get_header("action_dispatch.request.content_type") do |k|
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/
Mime::Type.lookup($1.strip.downcase) Mime::Type.lookup($1.strip.downcase)
else else
nil nil
end end
set_header k, v
end end
end end
@ -30,14 +31,15 @@ def content_type
# Returns the accepted MIME type for the request. # Returns the accepted MIME type for the request.
def accepts def accepts
@env["action_dispatch.request.accepts"] ||= begin get_header("action_dispatch.request.accepts") do |k|
header = @env['HTTP_ACCEPT'].to_s.strip header = get_header('HTTP_ACCEPT').to_s.strip
if header.empty? v = if header.empty?
[content_mime_type] [content_mime_type]
else else
Mime::Type.parse(header) Mime::Type.parse(header)
end end
set_header k, v
end end
end end
@ -52,14 +54,14 @@ def format(view_path = [])
end end
def formats def formats
@env["action_dispatch.request.formats"] ||= begin get_header("action_dispatch.request.formats") do |k|
params_readable = begin params_readable = begin
parameters[:format] parameters[:format]
rescue ActionController::BadRequest rescue ActionController::BadRequest
false false
end end
if params_readable v = if params_readable
Array(Mime[parameters[:format]]) Array(Mime[parameters[:format]])
elsif use_accept_header && valid_accept_header elsif use_accept_header && valid_accept_header
accepts accepts
@ -68,6 +70,7 @@ def formats
else else
[Mime::HTML] [Mime::HTML]
end end
set_header k, v
end end
end end
@ -102,7 +105,7 @@ def variant
# end # end
def format=(extension) def format=(extension)
parameters[:format] = extension.to_s parameters[:format] = extension.to_s
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
end end
# Sets the \formats by string extensions. This differs from #format= by allowing you # Sets the \formats by string extensions. This differs from #format= by allowing you
@ -121,9 +124,9 @@ def format=(extension)
# end # end
def formats=(extensions) def formats=(extensions)
parameters[:format] = extensions.first.to_s parameters[:format] = extensions.first.to_s
@env["action_dispatch.request.formats"] = extensions.collect do |extension| set_header "action_dispatch.request.formats", extensions.collect { |extension|
Mime::Type.lookup_by_extension(extension) Mime::Type.lookup_by_extension(extension)
end }
end end
# Receives an array of mimes and return the first user sent mime that # Receives an array of mimes and return the first user sent mime that

@ -45,7 +45,7 @@ def fetch(type)
# #
# respond_to do |format| # respond_to do |format|
# format.html # format.html
# format.ics { render text: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") } # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post } # format.xml { render xml: @post }
# end # end
# end # end
@ -211,7 +211,7 @@ def parse_data_with_trailing_star(input)
# This method is opposite of register method. # This method is opposite of register method.
# #
# Usage: # To unregister a MIME type:
# #
# Mime::Type.unregister(:mobile) # Mime::Type.unregister(:mobile)
def unregister(symbol) def unregister(symbol)

@ -34,11 +34,11 @@ def self.compile(filters)
end end
end end
deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.") } deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) }
deep_strings, strings = strings.partition { |s| s.include?("\\.") } deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) }
regexps << Regexp.new(strings.join('|'), true) unless strings.empty? regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
deep_regexps << Regexp.new(deep_strings.join('|'), true) unless deep_strings.empty? deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?
new regexps, deep_regexps, blocks new regexps, deep_regexps, blocks
end end

@ -8,20 +8,23 @@ module Parameters
# Returns both GET and POST \parameters in a single hash. # Returns both GET and POST \parameters in a single hash.
def parameters def parameters
@env["action_dispatch.request.parameters"] ||= begin params = get_header("action_dispatch.request.parameters")
params = begin return params if params
request_parameters.merge(query_parameters)
rescue EOFError params = begin
query_parameters.dup request_parameters.merge(query_parameters)
end rescue EOFError
params.merge!(path_parameters) query_parameters.dup
end end
params.merge!(path_parameters)
set_header("action_dispatch.request.parameters", params)
params
end end
alias :params :parameters alias :params :parameters
def path_parameters=(parameters) #:nodoc: def path_parameters=(parameters) #:nodoc:
@env.delete('action_dispatch.request.parameters') delete_header('action_dispatch.request.parameters')
@env[PARAMETERS_KEY] = parameters set_header PARAMETERS_KEY, parameters
end end
# Returns a hash with the \parameters used to form the \path of the request. # Returns a hash with the \parameters used to form the \path of the request.
@ -29,7 +32,7 @@ def path_parameters=(parameters) #:nodoc:
# #
# {'action' => 'my_action', 'controller' => 'my_controller'} # {'action' => 'my_action', 'controller' => 'my_controller'}
def path_parameters def path_parameters
@env[PARAMETERS_KEY] ||= {} get_header(PARAMETERS_KEY) || {}
end end
private private
@ -37,22 +40,7 @@ def path_parameters
# Convert nested Hash to HashWithIndifferentAccess. # Convert nested Hash to HashWithIndifferentAccess.
# #
def normalize_encode_params(params) def normalize_encode_params(params)
case params ActionDispatch::Request::Utils.normalize_encode_params params
when Hash
if params.has_key?(:tempfile)
UploadedFile.new(params)
else
params.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = if val.is_a?(Array)
val.map! { |el| normalize_encode_params(el) }
else
normalize_encode_params(val)
end
end.with_indifferent_access
end
else
params
end
end end
end end
end end

@ -20,8 +20,6 @@ class Request < Rack::Request
include ActionDispatch::Http::FilterParameters include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL include ActionDispatch::Http::URL
HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc:
autoload :Session, 'action_dispatch/request/session' autoload :Session, 'action_dispatch/request/session'
autoload :Utils, 'action_dispatch/request/utils' autoload :Utils, 'action_dispatch/request/utils'
@ -31,15 +29,19 @@ class Request < Rack::Request
PATH_TRANSLATED REMOTE_HOST PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL SERVER_NAME SERVER_PROTOCOL
ORIGINAL_SCRIPT_NAME
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA ].freeze HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
HTTP_X_FORWARDED_FOR HTTP_VERSION
HTTP_X_REQUEST_ID
].freeze
ENV_METHODS.each do |env| ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1 class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
@env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"] get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze
end # end end # end
METHOD METHOD
end end
@ -65,8 +67,20 @@ def check_path_parameters!
end end
end end
def controller_class
check_path_parameters!
params = path_parameters
controller_param = params[:controller].underscore if params.key?(:controller)
params[:action] ||= 'index'
yield unless controller_param
const_name = "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
end
def key?(key) def key?(key)
@env.key?(key) has_header? key
end end
# List of HTTP request methods from the following RFCs: # List of HTTP request methods from the following RFCs:
@ -103,27 +117,46 @@ def key?(key)
# the application should use), this \method returns the overridden # the application should use), this \method returns the overridden
# value, not the original. # value, not the original.
def request_method def request_method
@request_method ||= check_method(env["REQUEST_METHOD"]) @request_method ||= check_method(super)
end end
def routes # :nodoc: def routes # :nodoc:
env["action_dispatch.routes".freeze] get_header("action_dispatch.routes".freeze)
end end
def original_script_name # :nodoc: def routes=(routes) # :nodoc:
env['ORIGINAL_SCRIPT_NAME'.freeze] set_header("action_dispatch.routes".freeze, routes)
end end
def engine_script_name(_routes) # :nodoc: def engine_script_name(_routes) # :nodoc:
env[_routes.env_key] get_header(_routes.env_key)
end
def engine_script_name=(name) # :nodoc:
set_header(routes.env_key, name.dup)
end end
def request_method=(request_method) #:nodoc: def request_method=(request_method) #:nodoc:
if check_method(request_method) if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method @request_method = set_header("REQUEST_METHOD", request_method)
end end
end end
def controller_instance # :nodoc:
get_header('action_controller.instance'.freeze)
end
def controller_instance=(controller) # :nodoc:
set_header('action_controller.instance'.freeze, controller)
end
def show_exceptions? # :nodoc:
# We're treating `nil` as "unset", and we want the default setting to be
# `true`. This logic should be extracted to `env_config` and calculated
# once.
!(get_header('action_dispatch.show_exceptions'.freeze) == false)
end
# Returns a symbol form of the #request_method # Returns a symbol form of the #request_method
def request_method_symbol def request_method_symbol
HTTP_METHOD_LOOKUP[request_method] HTTP_METHOD_LOOKUP[request_method]
@ -133,7 +166,7 @@ def request_method_symbol
# even if it was overridden by middleware. See #request_method for # even if it was overridden by middleware. See #request_method for
# more information. # more information.
def method def method
@method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']) @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD'))
end end
# Returns a symbol form of the #method # Returns a symbol form of the #method
@ -145,7 +178,7 @@ def method_symbol
# #
# request.headers["Content-Type"] # => "text/plain" # request.headers["Content-Type"] # => "text/plain"
def headers def headers
@headers ||= Http::Headers.new(@env) @headers ||= Http::Headers.new(self)
end end
# Returns a +String+ with the last requested path including their params. # Returns a +String+ with the last requested path including their params.
@ -156,7 +189,7 @@ def headers
# # get '/foo?bar' # # get '/foo?bar'
# request.original_fullpath # => '/foo?bar' # request.original_fullpath # => '/foo?bar'
def original_fullpath def original_fullpath
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
end end
# Returns the +String+ full path including params of the last URL requested. # Returns the +String+ full path including params of the last URL requested.
@ -195,7 +228,7 @@ def content_length
# (case-insensitive), which may need to be manually added depending on the # (case-insensitive), which may need to be manually added depending on the
# choice of JavaScript libraries and frameworks. # choice of JavaScript libraries and frameworks.
def xml_http_request? def xml_http_request?
@env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i
end end
alias :xhr? :xml_http_request? alias :xhr? :xml_http_request?
@ -207,7 +240,11 @@ def ip
# Returns the IP address of client as a +String+, # Returns the IP address of client as a +String+,
# usually set by the RemoteIp middleware. # usually set by the RemoteIp middleware.
def remote_ip def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
def remote_ip=(remote_ip)
set_header "action_dispatch.remote_ip".freeze, remote_ip
end end
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc: ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
@ -219,43 +256,39 @@ def remote_ip
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
# This relies on the rack variable set by the ActionDispatch::RequestId middleware. # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
def request_id def request_id
env[ACTION_DISPATCH_REQUEST_ID] get_header ACTION_DISPATCH_REQUEST_ID
end end
def request_id=(id) # :nodoc: def request_id=(id) # :nodoc:
env[ACTION_DISPATCH_REQUEST_ID] = id set_header ACTION_DISPATCH_REQUEST_ID, id
end end
alias_method :uuid, :request_id alias_method :uuid, :request_id
def x_request_id # :nodoc:
@env[HTTP_X_REQUEST_ID]
end
# Returns the lowercase name of the HTTP server software. # Returns the lowercase name of the HTTP server software.
def server_software def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil (get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil
end end
# Read the request \body. This is useful for web services that need to # Read the request \body. This is useful for web services that need to
# work with raw requests directly. # work with raw requests directly.
def raw_post def raw_post
unless @env.include? 'RAW_POST_DATA' unless has_header? 'RAW_POST_DATA'
raw_post_body = body raw_post_body = body
@env['RAW_POST_DATA'] = raw_post_body.read(content_length) set_header('RAW_POST_DATA', raw_post_body.read(content_length))
raw_post_body.rewind if raw_post_body.respond_to?(:rewind) raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end end
@env['RAW_POST_DATA'] get_header 'RAW_POST_DATA'
end end
# The request body is an IO input stream. If the RAW_POST_DATA environment # The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO. # variable is already set, wrap it in a StringIO.
def body def body
if raw_post = @env['RAW_POST_DATA'] if raw_post = get_header('RAW_POST_DATA')
raw_post.force_encoding(Encoding::BINARY) raw_post.force_encoding(Encoding::BINARY)
StringIO.new(raw_post) StringIO.new(raw_post)
else else
@env['rack.input'] body_stream
end end
end end
@ -266,7 +299,7 @@ def form_data?
end end
def body_stream #:nodoc: def body_stream #:nodoc:
@env['rack.input'] get_header('rack.input')
end end
# TODO This should be broken apart into AD::Request::Session and probably # TODO This should be broken apart into AD::Request::Session and probably
@ -277,20 +310,20 @@ def reset_session
else else
self.session = {} self.session = {}
end end
@env['action_dispatch.request.flash_hash'] = nil set_header('action_dispatch.request.flash_hash', nil)
end end
def session=(session) #:nodoc: def session=(session) #:nodoc:
Session.set @env, session Session.set self, session
end end
def session_options=(options) def session_options=(options)
Session::Options.set @env, options Session::Options.set self, options
end end
# Override Rack's GET method to support indifferent access # Override Rack's GET method to support indifferent access
def GET def GET
@env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) @env["action_dispatch.request.query_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:query, e) raise ActionController::BadRequest.new(:query, e)
end end
@ -298,7 +331,7 @@ def GET
# Override Rack's POST method to support indifferent access # Override Rack's POST method to support indifferent access
def POST def POST
@env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) @env["action_dispatch.request.request_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e) raise ActionController::BadRequest.new(:request, e)
end end
@ -307,10 +340,10 @@ def POST
# Returns the authorization header regardless of whether it was specified directly or through one of the # Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives. # proxy alternatives.
def authorization def authorization
@env['HTTP_AUTHORIZATION'] || get_header('HTTP_AUTHORIZATION') ||
@env['X-HTTP_AUTHORIZATION'] || get_header('X-HTTP_AUTHORIZATION') ||
@env['X_HTTP_AUTHORIZATION'] || get_header('X_HTTP_AUTHORIZATION') ||
@env['REDIRECT_X_HTTP_AUTHORIZATION'] get_header('REDIRECT_X_HTTP_AUTHORIZATION')
end end
# True if the request came from localhost, 127.0.0.1. # True if the request came from localhost, 127.0.0.1.
@ -318,10 +351,13 @@ def local?
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end end
protected def request_parameters=(params)
def parse_query(*) set_header("action_dispatch.request.request_parameters".freeze, params)
Utils.deep_munge(super) end
end
def logger
get_header("action_dispatch.logger".freeze)
end
private private
def check_method(name) def check_method(name)

@ -80,11 +80,21 @@ def initialize(response, buf)
@response = response @response = response
@buf = buf @buf = buf
@closed = false @closed = false
@str_body = nil
end
def body
@str_body ||= begin
buf = ''
each { |chunk| buf << chunk }
buf
end
end end
def write(string) def write(string)
raise IOError, "closed stream" if closed? raise IOError, "closed stream" if closed?
@str_body = nil
@response.commit! @response.commit!
@buf.push string @buf.push string
end end
@ -187,13 +197,13 @@ def content_type=(content_type)
@content_type = content_type.to_s @content_type = content_type.to_s
end end
# Sets the HTTP character set. # Sets the HTTP character set. In case of nil parameter
# it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
def charset=(charset) def charset=(charset)
if nil == charset @charset = charset.nil? ? self.class.default_charset : charset
@charset = self.class.default_charset
else
@charset = charset
end
end end
# The response code of the request. # The response code of the request.
@ -222,9 +232,7 @@ def message
# Returns the content of the response as a string. This contains the contents # Returns the content of the response as a string. This contains the contents
# of any calls to <tt>render</tt>. # of any calls to <tt>render</tt>.
def body def body
strings = [] @stream.body
each { |part| strings << part.to_s }
strings.join
end end
EMPTY = " " EMPTY = " "

@ -28,7 +28,13 @@ def initialize(hash) # :nodoc:
raise(ArgumentError, ':tempfile is required') unless @tempfile raise(ArgumentError, ':tempfile is required') unless @tempfile
@original_filename = hash[:filename] @original_filename = hash[:filename]
@original_filename &&= @original_filename.encode "UTF-8" if @original_filename
begin
@original_filename.encode!(Encoding::UTF_8)
rescue EncodingError
@original_filename.force_encoding(Encoding::UTF_8)
end
end
@content_type = hash[:type] @content_type = hash[:type]
@headers = hash[:head] @headers = hash[:head]
end end

@ -245,7 +245,7 @@ def raw_host_with_port
# req = Request.new 'HTTP_HOST' => 'example.com:8080' # req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com" # req.host # => "example.com"
def host def host
raw_host_with_port.sub(/:\d+$/, '') raw_host_with_port.sub(/:\d+$/, ''.freeze)
end end
# Returns a \host:\port string for this request, such as "example.com" or # Returns a \host:\port string for this request, such as "example.com" or

@ -14,7 +14,7 @@ def initialize(routes)
def generate(name, options, path_parameters, parameterize = nil) def generate(name, options, path_parameters, parameterize = nil)
constraints = path_parameters.merge(options) constraints = path_parameters.merge(options)
missing_keys = [] missing_keys = nil # need for variable scope
match_route(name, constraints) do |route| match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize) parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
@ -25,22 +25,22 @@ def generate(name, options, path_parameters, parameterize = nil)
next unless name || route.dispatcher? next unless name || route.dispatcher?
missing_keys = missing_keys(route, parameterized_parts) missing_keys = missing_keys(route, parameterized_parts)
next unless missing_keys.empty? next if missing_keys && !missing_keys.empty?
params = options.dup.delete_if do |key, _| params = options.dup.delete_if do |key, _|
parameterized_parts.key?(key) || route.defaults.key?(key) parameterized_parts.key?(key) || route.defaults.key?(key)
end end
defaults = route.defaults defaults = route.defaults
required_parts = route.required_parts required_parts = route.required_parts
parameterized_parts.delete_if do |key, value| parameterized_parts.keep_if do |key, value|
value.to_s == defaults[key].to_s && !required_parts.include?(key) defaults[key].nil? || value.to_s != defaults[key].to_s || required_parts.include?(key)
end end
return [route.format(parameterized_parts), params] return [route.format(parameterized_parts), params]
end end
message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}" message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty? message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
raise ActionController::UrlGenerationError, message raise ActionController::UrlGenerationError, message
end end
@ -54,12 +54,12 @@ def clear
def extract_parameterized_parts(route, options, recall, parameterize = nil) def extract_parameterized_parts(route, options, recall, parameterize = nil)
parameterized_parts = recall.merge(options) parameterized_parts = recall.merge(options)
keys_to_keep = route.parts.reverse.drop_while { |part| keys_to_keep = route.parts.reverse_each.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil? !options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts } | route.required_parts
(parameterized_parts.keys - keys_to_keep).each do |bad_key| parameterized_parts.delete_if do |bad_key, _|
parameterized_parts.delete(bad_key) !keys_to_keep.include?(bad_key)
end end
if parameterize if parameterize
@ -110,15 +110,36 @@ def non_recursive(cache, options)
routes routes
end end
module RegexCaseComparator
DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
def self.===(regex)
DEFAULT_INPUT == regex
end
end
# Returns an array populated with missing keys if any are present. # Returns an array populated with missing keys if any are present.
def missing_keys(route, parts) def missing_keys(route, parts)
missing_keys = [] missing_keys = nil
tests = route.path.requirements tests = route.path.requirements
route.required_parts.each { |key| route.required_parts.each { |key|
if tests.key?(key) case tests[key]
missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key] when nil
unless parts[key]
missing_keys ||= []
missing_keys << key
end
when RegexCaseComparator
unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
missing_keys ||= []
missing_keys << key
end
else else
missing_keys << key unless parts[key] unless /\A#{tests[key]}\Z/ === parts[key]
missing_keys ||= []
missing_keys << key
end
end end
} }
missing_keys missing_keys
@ -134,7 +155,7 @@ def possibles(cache, options, depth = 0)
def build_cache def build_cache
root = { ___routes: [] } root = { ___routes: [] }
routes.each_with_index do |route, i| routes.routes.each_with_index do |route, i|
leaf = route.required_defaults.inject(root) do |h, tuple| leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {} h[tuple] ||= {}
end end

@ -14,15 +14,15 @@ def initialize(left)
end end
def each(&block) def each(&block)
Visitors::Each.new(block).accept(self) Visitors::Each::INSTANCE.accept(self, block)
end end
def to_s def to_s
Visitors::String.new.accept(self) Visitors::String::INSTANCE.accept(self, '')
end end
def to_dot def to_dot
Visitors::Dot.new.accept(self) Visitors::Dot::INSTANCE.accept(self)
end end
def to_sym def to_sym
@ -30,7 +30,7 @@ def to_sym
end end
def name def name
left.tr '*:', '' left.tr '*:'.freeze, ''.freeze
end end
def type def type
@ -39,10 +39,14 @@ def type
def symbol?; false; end def symbol?; false; end
def literal?; false; end def literal?; false; end
def terminal?; false; end
def star?; false; end
def cat?; false; end
end end
class Terminal < Node # :nodoc: class Terminal < Node # :nodoc:
alias :symbol :left alias :symbol :left
def terminal?; true; end
end end
class Literal < Terminal # :nodoc: class Literal < Terminal # :nodoc:
@ -69,11 +73,13 @@ def type; :#{t.upcase}; end
class Symbol < Terminal # :nodoc: class Symbol < Terminal # :nodoc:
attr_accessor :regexp attr_accessor :regexp
alias :symbol :regexp alias :symbol :regexp
attr_reader :name
DEFAULT_EXP = /[^\.\/\?]+/ DEFAULT_EXP = /[^\.\/\?]+/
def initialize(left) def initialize(left)
super super
@regexp = DEFAULT_EXP @regexp = DEFAULT_EXP
@name = left.tr '*:'.freeze, ''.freeze
end end
def default_regexp? def default_regexp?
@ -92,6 +98,7 @@ def type; :GROUP; end
end end
class Star < Unary # :nodoc: class Star < Unary # :nodoc:
def star?; true; end
def type; :STAR; end def type; :STAR; end
def name def name
@ -111,6 +118,7 @@ def children; [left, right] end
end end
class Cat < Binary # :nodoc: class Cat < Binary # :nodoc:
def cat?; true; end
def type; :CAT; end def type; :CAT; end
end end

@ -6,6 +6,10 @@ module Journey # :nodoc:
class Parser < Racc::Parser # :nodoc: class Parser < Racc::Parser # :nodoc:
include Journey::Nodes include Journey::Nodes
def self.parse(string)
new.parse string
end
def initialize def initialize
@scanner = Scanner.new @scanner = Scanner.new
end end

@ -1,5 +1,3 @@
require 'action_dispatch/journey/router/strexp'
module ActionDispatch module ActionDispatch
module Journey # :nodoc: module Journey # :nodoc:
module Path # :nodoc: module Path # :nodoc:
@ -7,14 +5,20 @@ class Pattern # :nodoc:
attr_reader :spec, :requirements, :anchored attr_reader :spec, :requirements, :anchored
def self.from_string string def self.from_string string
new Journey::Router::Strexp.build(string, {}, ["/.?"], true) build(string, {}, "/.?", true)
end end
def initialize(strexp) def self.build(path, requirements, separators, anchored)
@spec = strexp.ast parser = Journey::Parser.new
@requirements = strexp.requirements ast = parser.parse path
@separators = strexp.separators.join new ast, requirements, separators, anchored
@anchored = strexp.anchor end
def initialize(ast, requirements, separators, anchored)
@spec = ast
@requirements = requirements
@separators = separators
@anchored = anchored
@names = nil @names = nil
@optional_names = nil @optional_names = nil
@ -28,12 +32,12 @@ def build_formatter
end end
def ast def ast
@spec.grep(Nodes::Symbol).each do |node| @spec.find_all(&:symbol?).each do |node|
re = @requirements[node.to_sym] re = @requirements[node.to_sym]
node.regexp = re if re node.regexp = re if re
end end
@spec.grep(Nodes::Star).each do |node| @spec.find_all(&:star?).each do |node|
node = node.left node = node.left
node.regexp = @requirements[node.to_sym] || /(.+)/ node.regexp = @requirements[node.to_sym] || /(.+)/
end end
@ -55,31 +59,6 @@ def optional_names
}.map(&:name).uniq }.map(&:name).uniq
end end
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
attr_reader :offsets
def initialize(matchers)
@matchers = matchers
@capture_count = [0]
end
def visit(node)
super
@capture_count
end
def visit_SYMBOL(node)
node = node.to_sym
if @matchers.key?(node)
re = /#{@matchers[node]}|/
@capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
else
@capture_count << (@capture_count.last || 0)
end
end
end
class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc: class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
def initialize(separator, matchers) def initialize(separator, matchers)
@separator = separator @separator = separator
@ -189,8 +168,20 @@ def regexp_visitor
def offsets def offsets
return @offsets if @offsets return @offsets if @offsets
viz = RegexpOffsets.new(@requirements) @offsets = [0]
@offsets = viz.accept(spec)
spec.find_all(&:symbol?).each do |node|
node = node.to_sym
if @requirements.key?(node)
re = /#{@requirements[node]}|/
@offsets.push((re.match('').length - 1) + @offsets.last)
else
@offsets << @offsets.last
end
end
@offsets
end end
end end
end end

@ -1,36 +1,81 @@
module ActionDispatch module ActionDispatch
module Journey # :nodoc: module Journey # :nodoc:
class Route # :nodoc: class Route # :nodoc:
attr_reader :app, :path, :defaults, :name attr_reader :app, :path, :defaults, :name, :precedence
attr_reader :constraints attr_reader :constraints
alias :conditions :constraints alias :conditions :constraints
attr_accessor :precedence module VerbMatchers
VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
VERBS.each do |v|
class_eval <<-eoc
class #{v}
def self.verb; name.split("::").last; end
def self.call(req); req.#{v.downcase}?; end
end
eoc
end
class Unknown
attr_reader :verb
def initialize(verb)
@verb = verb
end
def call(request); @verb === request.request_method; end
end
class All
def self.call(_); true; end
def self.verb; ''; end
end
VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash|
klass = const_get verb
hash[verb] = klass
hash[verb.downcase] = klass
hash[verb.downcase.to_sym] = klass
end
end
def self.verb_matcher(verb)
VerbMatchers::VERB_TO_CLASS.fetch(verb) do
VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
end
end
def self.build(name, app, path, constraints, required_defaults, defaults)
request_method_match = verb_matcher(constraints.delete(:request_method))
new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
end
## ##
# +path+ is a path constraint. # +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route. # +constraints+ is a hash of constraints to be applied to this route.
def initialize(name, app, path, constraints, required_defaults, defaults) def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence)
@name = name @name = name
@app = app @app = app
@path = path @path = path
@request_method_match = request_method_match
@constraints = constraints @constraints = constraints
@defaults = defaults @defaults = defaults
@required_defaults = nil @required_defaults = nil
@_required_defaults = required_defaults || [] @_required_defaults = required_defaults
@required_parts = nil @required_parts = nil
@parts = nil @parts = nil
@decorated_ast = nil @decorated_ast = nil
@precedence = 0 @precedence = precedence
@path_formatter = @path.build_formatter @path_formatter = @path.build_formatter
end end
def ast def ast
@decorated_ast ||= begin @decorated_ast ||= begin
decorated_ast = path.ast decorated_ast = path.ast
decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self } decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
decorated_ast decorated_ast
end end
end end
@ -92,7 +137,8 @@ def dispatcher?
end end
def matches?(request) def matches?(request)
constraints.all? do |method, value| match_verb(request) &&
constraints.all? { |method, value|
case value case value
when Regexp, String when Regexp, String
value === request.send(method).to_s value === request.send(method).to_s
@ -105,15 +151,28 @@ def matches?(request)
else else
value === request.send(method) value === request.send(method)
end end
end }
end end
def ip def ip
constraints[:ip] || // constraints[:ip] || //
end end
def requires_matching_verb?
!@request_method_match.all? { |x| x == VerbMatchers::All }
end
def verb def verb
constraints[:request_method] || // %r[^#{verbs.join('|')}$]
end
private
def verbs
@request_method_match.map(&:verb)
end
def match_verb(request)
@request_method_match.any? { |m| m.call request }
end end
end end
end end

@ -1,5 +1,4 @@
require 'action_dispatch/journey/router/utils' require 'action_dispatch/journey/router/utils'
require 'action_dispatch/journey/router/strexp'
require 'action_dispatch/journey/routes' require 'action_dispatch/journey/routes'
require 'action_dispatch/journey/formatter' require 'action_dispatch/journey/formatter'
@ -102,7 +101,7 @@ def find_routes req
} }
routes = routes =
if req.request_method == "HEAD" if req.head?
match_head_routes(routes, req) match_head_routes(routes, req)
else else
match_routes(routes, req) match_routes(routes, req)
@ -121,7 +120,7 @@ def find_routes req
end end
def match_head_routes(routes, req) def match_head_routes(routes, req)
verb_specific_routes = routes.reject { |route| route.verb == // } verb_specific_routes = routes.select(&:requires_matching_verb?)
head_routes = match_routes(verb_specific_routes, req) head_routes = match_routes(verb_specific_routes, req)
if head_routes.empty? if head_routes.empty?

@ -1,27 +0,0 @@
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
class Strexp # :nodoc:
class << self
alias :compile :new
end
attr_reader :path, :requirements, :separators, :anchor, :ast
def self.build(path, requirements, separators, anchor = true)
parser = Journey::Parser.new
ast = parser.parse path
new ast, path, requirements, separators, anchor
end
def initialize(ast, path, requirements, separators, anchor = true)
@ast = ast
@path = path
@requirements = requirements
@separators = separators
@anchor = anchor
end
end
end
end
end

@ -14,10 +14,10 @@ class Utils # :nodoc:
# normalize_path("/%ab") # => "/%AB" # normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path) def self.normalize_path(path)
path = "/#{path}" path = "/#{path}"
path.squeeze!('/') path.squeeze!('/'.freeze)
path.sub!(%r{/+\Z}, '') path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
path = '/' if path == '' path = '/' if path == ''.freeze
path path
end end

@ -5,11 +5,10 @@ module Journey # :nodoc:
class Routes # :nodoc: class Routes # :nodoc:
include Enumerable include Enumerable
attr_reader :routes, :named_routes, :custom_routes, :anchored_routes attr_reader :routes, :custom_routes, :anchored_routes
def initialize def initialize
@routes = [] @routes = []
@named_routes = {}
@ast = nil @ast = nil
@anchored_routes = [] @anchored_routes = []
@custom_routes = [] @custom_routes = []
@ -37,7 +36,6 @@ def clear
routes.clear routes.clear
anchored_routes.clear anchored_routes.clear
custom_routes.clear custom_routes.clear
named_routes.clear
end end
def partition_route(route) def partition_route(route)
@ -62,13 +60,9 @@ def simulator
end end
end end
# Add a route to the routing table. def add_route(name, mapping)
def add_route(app, path, conditions, required_defaults, defaults, name = nil) route = mapping.make_route name, routes.length
route = Route.new(name, app, path, conditions, required_defaults, defaults)
route.precedence = routes.length
routes << route routes << route
named_routes[name] = route if name && !named_routes[name]
partition_route(route) partition_route(route)
clear_cache! clear_cache!
route route

@ -92,6 +92,45 @@ def visit_DOT(n); terminal(n); end
end end
end end
class FunctionalVisitor # :nodoc:
DISPATCH_CACHE = {}
def accept(node, seed)
visit(node, seed)
end
def visit node, seed
send(DISPATCH_CACHE[node.type], node, seed)
end
def binary(node, seed)
visit(node.right, visit(node.left, seed))
end
def visit_CAT(n, seed); binary(n, seed); end
def nary(node, seed)
node.children.inject(seed) { |s, c| visit(c, s) }
end
def visit_OR(n, seed); nary(n, seed); end
def unary(node, seed)
visit(node.left, seed)
end
def visit_GROUP(n, seed); unary(n, seed); end
def visit_STAR(n, seed); unary(n, seed); end
def terminal(node, seed); seed; end
def visit_LITERAL(n, seed); terminal(n, seed); end
def visit_SYMBOL(n, seed); terminal(n, seed); end
def visit_SLASH(n, seed); terminal(n, seed); end
def visit_DOT(n, seed); terminal(n, seed); end
instance_methods(false).each do |pim|
next unless pim =~ /^visit_(.*)$/
DISPATCH_CACHE[$1.to_sym] = pim
end
end
class FormatBuilder < Visitor # :nodoc: class FormatBuilder < Visitor # :nodoc:
def accept(node); Journey::Format.new(super); end def accept(node); Journey::Format.new(super); end
def terminal(node); [node.left]; end def terminal(node); [node.left]; end
@ -117,104 +156,110 @@ def visit_SYMBOL(n)
end end
# Loop through the requirements AST # Loop through the requirements AST
class Each < Visitor # :nodoc: class Each < FunctionalVisitor # :nodoc:
attr_reader :block def visit(node, block)
def initialize(block)
@block = block
end
def visit(node)
block.call(node) block.call(node)
super super
end end
INSTANCE = new
end end
class String < Visitor # :nodoc: class String < FunctionalVisitor # :nodoc:
private private
def binary(node) def binary(node, seed)
[visit(node.left), visit(node.right)].join visit(node.right, visit(node.left, seed))
end end
def nary(node) def nary(node, seed)
node.children.map { |c| visit(c) }.join '|' last_child = node.children.last
node.children.inject(seed) { |s, c|
string = visit(c, s)
string << "|".freeze unless last_child == c
string
}
end end
def terminal(node) def terminal(node, seed)
node.left seed + node.left
end end
def visit_GROUP(node) def visit_GROUP(node, seed)
"(#{visit(node.left)})" visit(node.left, seed << "(".freeze) << ")".freeze
end end
INSTANCE = new
end end
class Dot < Visitor # :nodoc: class Dot < FunctionalVisitor # :nodoc:
def initialize def initialize
@nodes = [] @nodes = []
@edges = [] @edges = []
end end
def accept(node) def accept(node, seed = [[], []])
super super
nodes, edges = seed
<<-eodot <<-eodot
digraph parse_tree { digraph parse_tree {
size="8,5" size="8,5"
node [shape = none]; node [shape = none];
edge [dir = none]; edge [dir = none];
#{@nodes.join "\n"} #{nodes.join "\n"}
#{@edges.join("\n")} #{edges.join("\n")}
} }
eodot eodot
end end
private private
def binary(node) def binary(node, seed)
node.children.each do |c| seed.last.concat node.children.map { |c|
@edges << "#{node.object_id} -> #{c.object_id};" "#{node.object_id} -> #{c.object_id};"
end }
super super
end end
def nary(node) def nary(node, seed)
node.children.each do |c| seed.last.concat node.children.map { |c|
@edges << "#{node.object_id} -> #{c.object_id};" "#{node.object_id} -> #{c.object_id};"
end }
super super
end end
def unary(node) def unary(node, seed)
@edges << "#{node.object_id} -> #{node.left.object_id};" seed.last << "#{node.object_id} -> #{node.left.object_id};"
super super
end end
def visit_GROUP(node) def visit_GROUP(node, seed)
@nodes << "#{node.object_id} [label=\"()\"];" seed.first << "#{node.object_id} [label=\"()\"];"
super super
end end
def visit_CAT(node) def visit_CAT(node, seed)
@nodes << "#{node.object_id} [label=\"\"];" seed.first << "#{node.object_id} [label=\"\"];"
super super
end end
def visit_STAR(node) def visit_STAR(node, seed)
@nodes << "#{node.object_id} [label=\"*\"];" seed.first << "#{node.object_id} [label=\"*\"];"
super super
end end
def visit_OR(node) def visit_OR(node, seed)
@nodes << "#{node.object_id} [label=\"|\"];" seed.first << "#{node.object_id} [label=\"|\"];"
super super
end end
def terminal(node) def terminal(node, seed)
value = node.left value = node.left
@nodes << "#{node.object_id} [label=\"#{value}\"];" seed.first << "#{node.object_id} [label=\"#{value}\"];"
seed
end end
INSTANCE = new
end end
end end
end end

@ -8,8 +8,52 @@
module ActionDispatch module ActionDispatch
class Request < Rack::Request class Request < Rack::Request
def cookie_jar def cookie_jar
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(env, host, ssl?, cookies) get_header('action_dispatch.cookies'.freeze) do |k|
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
end
end end
# :stopdoc:
def have_cookie_jar?
has_header? 'action_dispatch.cookies'.freeze
end
def cookie_jar=(jar)
set_header 'action_dispatch.cookies'.freeze, jar
end
def key_generator
get_header Cookies::GENERATOR_KEY
end
def signed_cookie_salt
get_header Cookies::SIGNED_COOKIE_SALT
end
def encrypted_cookie_salt
get_header Cookies::ENCRYPTED_COOKIE_SALT
end
def encrypted_signed_cookie_salt
get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
end
def secret_token
get_header Cookies::SECRET_TOKEN
end
def secret_key_base
get_header Cookies::SECRET_KEY_BASE
end
def cookies_serializer
get_header Cookies::COOKIES_SERIALIZER
end
def cookies_digest
get_header Cookies::COOKIES_DIGEST
end
# :startdoc:
end end
# \Cookies are read and written through ActionController#cookies. # \Cookies are read and written through ActionController#cookies.
@ -118,7 +162,7 @@ module ChainedCookieJars
# cookies.permanent.signed[:remember_me] = current_user.id # cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent def permanent
@permanent ||= PermanentCookieJar.new(self, @key_generator, @options) @permanent ||= PermanentCookieJar.new(self)
end end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@ -138,10 +182,10 @@ def permanent
# cookies.signed[:discount] # => 45 # cookies.signed[:discount] # => 45
def signed def signed
@signed ||= @signed ||=
if @options[:upgrade_legacy_signed_cookies] if upgrade_legacy_signed_cookies?
UpgradeLegacySignedCookieJar.new(self, @key_generator, @options) UpgradeLegacySignedCookieJar.new(self)
else else
SignedCookieJar.new(self, @key_generator, @options) SignedCookieJar.new(self)
end end
end end
@ -161,10 +205,10 @@ def signed
# cookies.encrypted[:discount] # => 45 # cookies.encrypted[:discount] # => 45
def encrypted def encrypted
@encrypted ||= @encrypted ||=
if @options[:upgrade_legacy_signed_cookies] if upgrade_legacy_signed_cookies?
UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options) UpgradeLegacyEncryptedCookieJar.new(self)
else else
EncryptedCookieJar.new(self, @key_generator, @options) EncryptedCookieJar.new(self)
end end
end end
@ -172,12 +216,26 @@ def encrypted
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
def signed_or_encrypted def signed_or_encrypted
@signed_or_encrypted ||= @signed_or_encrypted ||=
if @options[:secret_key_base].present? if request.secret_key_base.present?
encrypted encrypted
else else
signed signed
end end
end end
protected
def request; @parent_jar.request; end
private
def upgrade_legacy_signed_cookies?
request.secret_token.present? && request.secret_key_base.present?
end
def key_generator
request.key_generator
end
end end
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
@ -187,7 +245,7 @@ def signed_or_encrypted
module VerifyAndUpgradeLegacySignedMessage # :nodoc: module VerifyAndUpgradeLegacySignedMessage # :nodoc:
def initialize(*args) def initialize(*args)
super super
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer) @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end end
def verify_and_upgrade_legacy_signed_message(name, signed_message) def verify_and_upgrade_legacy_signed_message(name, signed_message)
@ -216,34 +274,18 @@ class CookieJar #:nodoc:
# $& => example.local # $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.options_for_env(env) #:nodoc: def self.build(req, cookies)
{ signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '', new(req).tap do |hash|
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
serializer: env[COOKIES_SERIALIZER],
digest: env[COOKIES_DIGEST]
}
end
def self.build(env, host, secure, cookies)
key_generator = env[GENERATOR_KEY]
options = options_for_env env
new(key_generator, host, secure, options).tap do |hash|
hash.update(cookies) hash.update(cookies)
end end
end end
def initialize(key_generator, host = nil, secure = false, options = {}) attr_reader :request
@key_generator = key_generator
def initialize(request)
@set_cookies = {} @set_cookies = {}
@delete_cookies = {} @delete_cookies = {}
@host = host @request = request
@secure = secure
@options = options
@cookies = {} @cookies = {}
@committed = false @committed = false
end end
@ -292,12 +334,12 @@ def handle_options(options) #:nodoc:
# if host is not ip and matches domain regexp # if host is not ip and matches domain regexp
# (ip confirms to domain regexp so we explicitly check for ip) # (ip confirms to domain regexp so we explicitly check for ip)
options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp) options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
".#{$&}" ".#{$&}"
end end
elsif options[:domain].is_a? Array elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it # if host matches one of the supplied domains without a dot in front of it
options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') } options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') }
end end
end end
@ -356,27 +398,20 @@ def write(headers)
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) } @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
end end
def recycle! #:nodoc:
@set_cookies = {}
@delete_cookies = {}
end
mattr_accessor :always_write_cookie mattr_accessor :always_write_cookie
self.always_write_cookie = false self.always_write_cookie = false
private private
def write_cookie?(cookie) def write_cookie?(cookie)
@secure || !cookie[:secure] || always_write_cookie request.ssl? || !cookie[:secure] || always_write_cookie
end end
end end
class PermanentCookieJar #:nodoc: class PermanentCookieJar #:nodoc:
include ChainedCookieJars include ChainedCookieJars
def initialize(parent_jar, key_generator, options = {}) def initialize(parent_jar)
@parent_jar = parent_jar @parent_jar = parent_jar
@key_generator = key_generator
@options = options
end end
def [](name) def [](name)
@ -410,7 +445,7 @@ module SerializedCookieJars # :nodoc:
protected protected
def needs_migration?(value) def needs_migration?(value)
@options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE) request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
end end
def serialize(value) def serialize(value)
@ -430,7 +465,7 @@ def deserialize(name, value)
end end
def serializer def serializer
serializer = @options[:serializer] || :marshal serializer = request.cookies_serializer || :marshal
case serializer case serializer
when :marshal when :marshal
Marshal Marshal
@ -442,7 +477,7 @@ def serializer
end end
def digest def digest
@options[:digest] || 'SHA1' request.cookies_digest || 'SHA1'
end end
end end
@ -450,10 +485,9 @@ class SignedCookieJar #:nodoc:
include ChainedCookieJars include ChainedCookieJars
include SerializedCookieJars include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {}) def initialize(parent_jar)
@parent_jar = parent_jar @parent_jar = parent_jar
@options = options secret = key_generator.generate_key(request.signed_cookie_salt)
secret = key_generator.generate_key(@options[:signed_cookie_salt])
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end end
@ -505,16 +539,16 @@ class EncryptedCookieJar #:nodoc:
include ChainedCookieJars include ChainedCookieJars
include SerializedCookieJars include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {}) def initialize(parent_jar)
@parent_jar = parent_jar
if ActiveSupport::LegacyKeyGenerator === key_generator if ActiveSupport::LegacyKeyGenerator === key_generator
raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " + raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option." "Read the upgrade documentation to learn more about this new config option."
end end
@parent_jar = parent_jar secret = key_generator.generate_key(request.encrypted_cookie_salt || '')
@options = options sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '')
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end end
@ -568,9 +602,12 @@ def initialize(app)
end end
def call(env) def call(env)
request = ActionDispatch::Request.new env
status, headers, body = @app.call(env) status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies'] if request.have_cookie_jar?
cookie_jar = request.cookie_jar
unless cookie_jar.committed? unless cookie_jar.committed?
cookie_jar.write(headers) cookie_jar.write(headers)
if headers[HTTP_HEADER].respond_to?(:join) if headers[HTTP_HEADER].respond_to?(:join)

@ -44,6 +44,7 @@ def initialize(app, routes_app = nil)
end end
def call(env) def call(env)
request = ActionDispatch::Request.new env
_, headers, body = response = @app.call(env) _, headers, body = response = @app.call(env)
if headers['X-Cascade'] == 'pass' if headers['X-Cascade'] == 'pass'
@ -53,18 +54,18 @@ def call(env)
response response
rescue Exception => exception rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false raise exception unless request.show_exceptions?
render_exception(env, exception) render_exception(request, exception)
end end
private private
def render_exception(env, exception) def render_exception(request, exception)
wrapper = ExceptionWrapper.new(env, exception) backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner')
log_error(env, wrapper) wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
log_error(request, wrapper)
if env['action_dispatch.show_detailed_exceptions'] if request.get_header('action_dispatch.show_detailed_exceptions')
request = Request.new(env)
traces = wrapper.traces traces = wrapper.traces
trace_to_show = 'Application Trace' trace_to_show = 'Application Trace'
@ -106,8 +107,8 @@ def render(status, body, format)
[status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end end
def log_error(env, wrapper) def log_error(request, wrapper)
logger = logger(env) logger = logger(request)
return unless logger return unless logger
exception = wrapper.exception exception = wrapper.exception
@ -123,8 +124,8 @@ def log_error(env, wrapper)
end end
end end
def logger(env) def logger(request)
env['action_dispatch.logger'] || stderr_logger request.logger || stderr_logger
end end
def stderr_logger def stderr_logger

@ -31,10 +31,10 @@ class ExceptionWrapper
'ActionView::Template::Error' => 'template_error' 'ActionView::Template::Error' => 'template_error'
) )
attr_reader :env, :exception, :line_number, :file attr_reader :backtrace_cleaner, :exception, :line_number, :file
def initialize(env, exception) def initialize(backtrace_cleaner, exception)
@env = env @backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception) @exception = original_exception(exception)
expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError) expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
@ -125,10 +125,6 @@ def clean_backtrace(*args)
end end
end end
def backtrace_cleaner
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
end
def source_fragment(path, line) def source_fragment(path, line)
return unless Rails.respond_to?(:root) && Rails.root return unless Rails.respond_to?(:root) && Rails.root
full_path = Rails.root.join(path) full_path = Rails.root.join(path)

@ -6,7 +6,17 @@ class Request < Rack::Request
# read a notice you put there or <tt>flash["notice"] = "hello"</tt> # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one. # to put a new one.
def flash def flash
@env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"]) flash = flash_hash
return flash if flash
self.flash = Flash::FlashHash.from_session_value(session["flash"])
end
def flash=(flash)
set_header Flash::KEY, flash
end
def flash_hash # :nodoc:
get_header Flash::KEY
end end
end end
@ -263,14 +273,15 @@ def initialize(app)
end end
def call(env) def call(env)
req = ActionDispatch::Request.new env
@app.call(env) @app.call(env)
ensure ensure
session = Request::Session.find(env) || {} session = Request::Session.find(req) || {}
flash_hash = env[KEY] flash_hash = req.flash_hash
if flash_hash && (flash_hash.present? || session.key?('flash')) if flash_hash && (flash_hash.present? || session.key?('flash'))
session["flash"] = flash_hash.to_session_value session["flash"] = flash_hash.to_session_value
env[KEY] = flash_hash.dup req.flash = flash_hash.dup
end end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)

@ -1,9 +1,14 @@
require 'active_support/core_ext/hash/conversions'
require 'action_dispatch/http/request' require 'action_dispatch/http/request'
require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch module ActionDispatch
# ActionDispatch::ParamsParser works for all the requests having any Content-Length
# (like POST). It takes raw data from the request and puts it through the parser
# that is picked based on Content-Type header.
#
# In case of any error while parsing data ParamsParser::ParseError is raised.
class ParamsParser class ParamsParser
# Raised when raw data from the request cannot be parsed by the parser
# defined for request's content mime type.
class ParseError < StandardError class ParseError < StandardError
attr_reader :original_exception attr_reader :original_exception
@ -17,39 +22,42 @@ def initialize(message, original_exception)
Mime::JSON => lambda { |raw_post| Mime::JSON => lambda { |raw_post|
data = ActiveSupport::JSON.decode(raw_post) data = ActiveSupport::JSON.decode(raw_post)
data = {:_json => data} unless data.is_a?(Hash) data = {:_json => data} unless data.is_a?(Hash)
Request::Utils.deep_munge(data).with_indifferent_access Request::Utils.normalize_encode_params(data)
} }
} }
# Create a new +ParamsParser+ middleware instance.
#
# The +parsers+ argument can take Hash of parsers where key is identifying
# content mime type, and value is a lambda that is going to process data.
def initialize(app, parsers = {}) def initialize(app, parsers = {})
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers) @app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end end
def call(env) def call(env)
default = env["action_dispatch.request.request_parameters"] request = Request.new(env)
env["action_dispatch.request.request_parameters"] = parse_formatted_parameters(env, @parsers, default)
request.request_parameters = parse_formatted_parameters(request, @parsers)
@app.call(env) @app.call(env)
end end
private private
def parse_formatted_parameters(env, parsers, default) def parse_formatted_parameters(request, parsers)
request = Request.new(env) return if request.content_length.zero?
return default if request.content_length.zero? strategy = parsers.fetch(request.content_mime_type) { return nil }
strategy = parsers.fetch(request.content_mime_type) { return default }
strategy.call(request.raw_post) strategy.call(request.raw_post)
rescue => e # JSON or Ruby code block errors rescue => e # JSON or Ruby code block errors
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e) raise ParseError.new(e.message, e)
end end
def logger(env) def logger(request)
env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr) request.logger || ActiveSupport::Logger.new($stderr)
end end
end end
end end

@ -17,8 +17,8 @@ def initialize(public_path)
end end
def call(env) def call(env)
status = env["PATH_INFO"][1..-1].to_i
request = ActionDispatch::Request.new(env) request = ActionDispatch::Request.new(env)
status = request.path_info[1..-1].to_i
content_type = request.formats.first content_type = request.formats.first
body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }

@ -74,16 +74,17 @@ def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
# requests. For those requests that do need to know the IP, the # requests. For those requests that do need to know the IP, the
# GetIp#calculate_ip method will calculate the memoized client IP address. # GetIp#calculate_ip method will calculate the memoized client IP address.
def call(env) def call(env)
env["action_dispatch.remote_ip"] = GetIp.new(env, check_ip, proxies) req = ActionDispatch::Request.new env
@app.call(env) req.remote_ip = GetIp.new(req, check_ip, proxies)
@app.call(req.env)
end end
# The GetIp class exists as a way to defer processing of the request data # The GetIp class exists as a way to defer processing of the request data
# into an actual IP address. If the ActionDispatch::Request#remote_ip method # into an actual IP address. If the ActionDispatch::Request#remote_ip method
# is called, this class will calculate the value and then memoize it. # is called, this class will calculate the value and then memoize it.
class GetIp class GetIp
def initialize(env, check_ip, proxies) def initialize(req, check_ip, proxies)
@env = env @req = req
@check_ip = check_ip @check_ip = check_ip
@proxies = proxies @proxies = proxies
end end
@ -108,11 +109,11 @@ def initialize(env, check_ip, proxies)
# the last address left, which was presumably set by one of those proxies. # the last address left, which was presumably set by one of those proxies.
def calculate_ip def calculate_ip
# Set by the Rack web server, this is a single value. # Set by the Rack web server, this is a single value.
remote_addr = ips_from('REMOTE_ADDR').last remote_addr = ips_from(@req.remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated. # Could be a CSV list and/or repeated headers that were concatenated.
client_ips = ips_from('HTTP_CLIENT_IP').reverse client_ips = ips_from(@req.client_ip).reverse
forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse forwarded_ips = ips_from(@req.x_forwarded_for).reverse
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
# If they are both set, it means that this request passed through two # If they are both set, it means that this request passed through two
@ -123,8 +124,8 @@ def calculate_ip
if should_check_ip && !forwarded_ips.include?(client_ips.last) if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user # We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " + raise IpSpoofAttackError, "IP spoofing attack?! " +
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " + "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end end
# We assume these things about the IP headers: # We assume these things about the IP headers:
@ -147,8 +148,9 @@ def to_s
protected protected
def ips_from(header) def ips_from(header)
return [] unless header
# Split the comma-separated list into an array of strings # Split the comma-separated list into an array of strings
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] ips = header.strip.split(/[,\s]+/)
ips.select do |ip| ips.select do |ip|
begin begin
# Only return IPs that are valid according to the IPAddr#new method # Only return IPs that are valid according to the IPAddr#new method

@ -36,6 +36,11 @@ def initialize_sid
@default_options.delete(:sidbits) @default_options.delete(:sidbits)
@default_options.delete(:secure_random) @default_options.delete(:secure_random)
end end
private
def make_request(env)
ActionDispatch::Request.new env
end
end end
module StaleSessionCheck module StaleSessionCheck
@ -65,8 +70,8 @@ def stale_session_check!
end end
module SessionObject # :nodoc: module SessionObject # :nodoc:
def prepare_session(env) def prepare_session(req)
Request::Session.create(self, env, @default_options) Request::Session.create(self, req, @default_options)
end end
def loaded_session?(session) def loaded_session?(session)
@ -81,8 +86,7 @@ class AbstractStore < Rack::Session::Abstract::ID
private private
def set_cookie(env, session_id, cookie) def set_cookie(request, session_id, cookie)
request = ActionDispatch::Request.new(env)
request.cookie_jar[key] = cookie request.cookie_jar[key] = cookie
end end
end end

@ -71,16 +71,16 @@ def initialize(app, options={})
super(app, options.merge!(:cookie_only => true)) super(app, options.merge!(:cookie_only => true))
end end
def destroy_session(env, session_id, options) def destroy_session(req, session_id, options)
new_sid = generate_sid unless options[:drop] new_sid = generate_sid unless options[:drop]
# Reset hash and Assign the new session id # Reset hash and Assign the new session id
env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {})
new_sid new_sid
end end
def load_session(env) def load_session(req)
stale_session_check! do stale_session_check! do
data = unpacked_cookie_data(env) data = unpacked_cookie_data(req)
data = persistent_session_id!(data) data = persistent_session_id!(data)
[data["session_id"], data] [data["session_id"], data]
end end
@ -88,20 +88,21 @@ def load_session(env)
private private
def extract_session_id(env) def extract_session_id(req)
stale_session_check! do stale_session_check! do
unpacked_cookie_data(env)["session_id"] unpacked_cookie_data(req)["session_id"]
end end
end end
def unpacked_cookie_data(env) def unpacked_cookie_data(req)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin req.get_header("action_dispatch.request.unsigned_session_cookie") do |k|
stale_session_check! do v = stale_session_check! do
if data = get_cookie(env) if data = get_cookie(req)
data.stringify_keys! data.stringify_keys!
end end
data || {} data || {}
end end
req.set_header k, v
end end
end end
@ -111,21 +112,20 @@ def persistent_session_id!(data, sid=nil)
data data
end end
def set_session(env, sid, session_data, options) def set_session(req, sid, session_data, options)
session_data["session_id"] = sid session_data["session_id"] = sid
session_data session_data
end end
def set_cookie(env, session_id, cookie) def set_cookie(request, session_id, cookie)
cookie_jar(env)[@key] = cookie cookie_jar(request)[@key] = cookie
end end
def get_cookie(env) def get_cookie(req)
cookie_jar(env)[@key] cookie_jar(req)[@key]
end end
def cookie_jar(env) def cookie_jar(request)
request = ActionDispatch::Request.new(env)
request.cookie_jar.signed_or_encrypted request.cookie_jar.signed_or_encrypted
end end
end end

@ -27,24 +27,26 @@ def initialize(app, exceptions_app)
end end
def call(env) def call(env)
request = ActionDispatch::Request.new env
@app.call(env) @app.call(env)
rescue Exception => exception rescue Exception => exception
if env['action_dispatch.show_exceptions'] == false if request.show_exceptions?
raise exception render_exception(request, exception)
else else
render_exception(env, exception) raise exception
end end
end end
private private
def render_exception(env, exception) def render_exception(request, exception)
wrapper = ExceptionWrapper.new(env, exception) backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner'
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
status = wrapper.status_code status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception request.set_header "action_dispatch.exception", wrapper.exception
env["action_dispatch.original_path"] = env["PATH_INFO"] request.set_header "action_dispatch.original_path", request.path_info
env["PATH_INFO"] = "/#{status}" request.path_info = "/#{status}"
response = @exceptions_app.call(env) response = @exceptions_app.call(request.env)
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
rescue Exception => failsafe_error rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"

@ -4,36 +4,15 @@
module ActionDispatch module ActionDispatch
class MiddlewareStack class MiddlewareStack
class Middleware class Middleware
attr_reader :args, :block, :name, :classcache attr_reader :args, :block, :klass
def initialize(klass_or_name, *args, &block) def initialize(klass, args, block)
@klass = nil @klass = klass
@args = args
if klass_or_name.respond_to?(:name) @block = block
@klass = klass_or_name
@name = @klass.name
else
@name = klass_or_name.to_s
end
@classcache = ActiveSupport::Dependencies::Reference
@args, @block = args, block
end end
def klass def name; klass.name; end
@klass || classcache[@name]
end
def ==(middleware)
case middleware
when Middleware
klass == middleware.klass
when Class
klass == middleware
else
normalize(@name) == normalize(middleware)
end
end
def inspect def inspect
klass.to_s klass.to_s
@ -42,12 +21,6 @@ def inspect
def build(app) def build(app)
klass.new(app, *args, &block) klass.new(app, *args, &block)
end end
private
def normalize(object)
object.to_s.strip.sub(/^::/, '')
end
end end
include Enumerable include Enumerable
@ -75,19 +48,17 @@ def [](i)
middlewares[i] middlewares[i]
end end
def unshift(*args, &block) def unshift(klass, *args, &block)
middleware = self.class::Middleware.new(*args, &block) middlewares.unshift(build_middleware(klass, args, block))
middlewares.unshift(middleware)
end end
def initialize_copy(other) def initialize_copy(other)
self.middlewares = other.middlewares.dup self.middlewares = other.middlewares.dup
end end
def insert(index, *args, &block) def insert(index, klass, *args, &block)
index = assert_index(index, :before) index = assert_index(index, :before)
middleware = self.class::Middleware.new(*args, &block) middlewares.insert(index, build_middleware(klass, args, block))
middlewares.insert(index, middleware)
end end
alias_method :insert_before, :insert alias_method :insert_before, :insert
@ -104,26 +75,48 @@ def swap(target, *args, &block)
end end
def delete(target) def delete(target)
middlewares.delete target target = get_class target
middlewares.delete_if { |m| m.klass == target }
end end
def use(*args, &block) def use(klass, *args, &block)
middleware = self.class::Middleware.new(*args, &block) middlewares.push(build_middleware(klass, args, block))
middlewares.push(middleware)
end end
def build(app = nil, &block) def build(app = Proc.new)
app ||= block
raise "MiddlewareStack#build requires an app" unless app
middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end end
protected protected
def assert_index(index, where) def assert_index(index, where)
i = index.is_a?(Integer) ? index : middlewares.index(index) index = get_class index
i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
raise "No such middleware to insert #{where}: #{index.inspect}" unless i raise "No such middleware to insert #{where}: #{index.inspect}" unless i
i i
end end
private
def get_class(klass)
if klass.is_a?(String) || klass.is_a?(Symbol)
classcache = ActiveSupport::Dependencies::Reference
converted_klass = classcache[klass.to_s]
ActiveSupport::Deprecation.warn <<-eowarn
Passing strings or symbols to the middleware builder is deprecated, please change
them to actual class references. For example:
"#{klass}" => #{converted_klass}
eowarn
converted_klass
else
klass
end
end
def build_middleware(klass, args, block)
Middleware.new(get_class(klass), args, block)
end
end end
end end

@ -35,7 +35,7 @@ def match?(path)
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"] paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p| if match = paths.detect { |p|
path = File.join(@root, p.force_encoding('UTF-8')) path = File.join(@root, p.force_encoding('UTF-8'.freeze))
begin begin
File.file?(path) && File.readable?(path) File.file?(path) && File.readable?(path)
rescue SystemCallError rescue SystemCallError
@ -48,26 +48,30 @@ def match?(path)
end end
def call(env) def call(env)
path = env['PATH_INFO'] serve ActionDispatch::Request.new env
end
def serve(request)
path = request.path_info
gzip_path = gzip_file_path(path) gzip_path = gzip_file_path(path)
if gzip_path && gzip_encoding_accepted?(env) if gzip_path && gzip_encoding_accepted?(request)
env['PATH_INFO'] = gzip_path request.path_info = gzip_path
status, headers, body = @file_server.call(env) status, headers, body = @file_server.call(request.env)
if status == 304 if status == 304
return [status, headers, body] return [status, headers, body]
end end
headers['Content-Encoding'] = 'gzip' headers['Content-Encoding'] = 'gzip'
headers['Content-Type'] = content_type(path) headers['Content-Type'] = content_type(path)
else else
status, headers, body = @file_server.call(env) status, headers, body = @file_server.call(request.env)
end end
headers['Vary'] = 'Accept-Encoding' if gzip_path headers['Vary'] = 'Accept-Encoding' if gzip_path
return [status, headers, body] return [status, headers, body]
ensure ensure
env['PATH_INFO'] = path request.path_info = path
end end
private private
@ -76,11 +80,11 @@ def ext
end end
def content_type(path) def content_type(path)
::Rack::Mime.mime_type(::File.extname(path), 'text/plain') ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
end end
def gzip_encoding_accepted?(env) def gzip_encoding_accepted?(request)
env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i request.accept_encoding =~ /\bgzip\b/i
end end
def gzip_file_path(path) def gzip_file_path(path)
@ -110,16 +114,17 @@ def initialize(app, path, cache_control = nil, index: 'index')
end end
def call(env) def call(env)
case env['REQUEST_METHOD'] req = ActionDispatch::Request.new env
when 'GET', 'HEAD'
path = env['PATH_INFO'].chomp('/') if req.get? || req.head?
path = req.path_info.chomp('/'.freeze)
if match = @file_handler.match?(path) if match = @file_handler.match?(path)
env['PATH_INFO'] = match req.path_info = match
return @file_handler.call(env) return @file_handler.serve(req)
end end
end end
@app.call(env) @app.call(req.env)
end end
end end
end end

@ -4,38 +4,38 @@ module ActionDispatch
class Request < Rack::Request class Request < Rack::Request
# Session is responsible for lazily loading the session from store. # Session is responsible for lazily loading the session from store.
class Session # :nodoc: class Session # :nodoc:
ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc: ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc: ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
# Singleton object used to determine if an optional param wasn't specified # Singleton object used to determine if an optional param wasn't specified
Unspecified = Object.new Unspecified = Object.new
# Creates a session hash, merging the properties of the previous session if any # Creates a session hash, merging the properties of the previous session if any
def self.create(store, env, default_options) def self.create(store, req, default_options)
session_was = find env session_was = find req
session = Request::Session.new(store, env) session = Request::Session.new(store, req)
session.merge! session_was if session_was session.merge! session_was if session_was
set(env, session) set(req, session)
Options.set(env, Request::Session::Options.new(store, default_options)) Options.set(req, Request::Session::Options.new(store, default_options))
session session
end end
def self.find(env) def self.find(req)
env[ENV_SESSION_KEY] req.get_header ENV_SESSION_KEY
end end
def self.set(env, session) def self.set(req, session)
env[ENV_SESSION_KEY] = session req.set_header ENV_SESSION_KEY, session
end end
class Options #:nodoc: class Options #:nodoc:
def self.set(env, options) def self.set(req, options)
env[ENV_SESSION_OPTIONS_KEY] = options req.set_header ENV_SESSION_OPTIONS_KEY, options
end end
def self.find(env) def self.find(req)
env[ENV_SESSION_OPTIONS_KEY] req.get_header ENV_SESSION_OPTIONS_KEY
end end
def initialize(by, default_options) def initialize(by, default_options)
@ -47,9 +47,9 @@ def [](key)
@delegate[key] @delegate[key]
end end
def id(env) def id(req)
@delegate.fetch(:id) { @delegate.fetch(:id) {
@by.send(:extract_session_id, env) @by.send(:extract_session_id, req)
} }
end end
@ -58,26 +58,26 @@ def to_hash; @delegate.dup; end
def values_at(*args); @delegate.values_at(*args); end def values_at(*args); @delegate.values_at(*args); end
end end
def initialize(by, env) def initialize(by, req)
@by = by @by = by
@env = env @req = req
@delegate = {} @delegate = {}
@loaded = false @loaded = false
@exists = nil # we haven't checked yet @exists = nil # we haven't checked yet
end end
def id def id
options.id(@env) options.id(@req)
end end
def options def options
Options.find @env Options.find @req
end end
def destroy def destroy
clear clear
options = self.options || {} options = self.options || {}
@by.send(:destroy_session, @env, options.id(@env), options) @by.send(:destroy_session, @req, options.id(@req), options)
# Load the new sid to be written with the response # Load the new sid to be written with the response
@loaded = false @loaded = false
@ -181,7 +181,7 @@ def inspect
def exists? def exists?
return @exists unless @exists.nil? return @exists unless @exists.nil?
@exists = @by.send(:session_exists?, @env) @exists = @by.send(:session_exists?, @req)
end end
def loaded? def loaded?
@ -209,7 +209,7 @@ def load_for_write!
end end
def load! def load!
id, session = @by.load_session @env id, session = @by.load_session @req
options[:id] = id options[:id] = id
@delegate.replace(stringify_keys(session)) @delegate.replace(stringify_keys(session))
@loaded = true @loaded = true

@ -5,24 +5,45 @@ class Utils # :nodoc:
mattr_accessor :perform_deep_munge mattr_accessor :perform_deep_munge
self.perform_deep_munge = true self.perform_deep_munge = true
class << self def self.normalize_encode_params(params)
# Remove nils from the params hash if perform_deep_munge
def deep_munge(hash, keys = []) NoNilParamEncoder.normalize_encode_params params
return hash unless perform_deep_munge else
ParamEncoder.normalize_encode_params params
end
end
hash.each do |k, v| class ParamEncoder # :nodoc:
keys << k # Convert nested Hash to HashWithIndifferentAccess.
case v #
when Array def self.normalize_encode_params(params)
v.grep(Hash) { |x| deep_munge(x, keys) } case params
v.compact! when Array
when Hash handle_array params
deep_munge(v, keys) when Hash
if params.has_key?(:tempfile)
ActionDispatch::Http::UploadedFile.new(params)
else
params.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = normalize_encode_params(val)
end.with_indifferent_access
end end
keys.pop else
params
end end
end
hash def self.handle_array(params)
params.map! { |el| normalize_encode_params(el) }
end
end
# Remove nils from the params hash
class NoNilParamEncoder < ParamEncoder # :nodoc:
def self.handle_array(params)
list = super
list.compact!
list
end end
end end
end end

File diff suppressed because it is too large Load Diff

@ -24,7 +24,7 @@ def call(env)
def serve(req) def serve(req)
req.check_path_parameters! req.check_path_parameters!
uri = URI.parse(path(req.path_parameters, req)) uri = URI.parse(path(req.path_parameters, req))
unless uri.host unless uri.host
if relative_path?(uri.path) if relative_path?(uri.path)
uri.path = "#{req.script_name}/#{uri.path}" uri.path = "#{req.script_name}/#{uri.path}"
@ -32,7 +32,7 @@ def serve(req)
uri.path = req.script_name.empty? ? "/" : req.script_name uri.path = req.script_name.empty? ? "/" : req.script_name
end end
end end
uri.scheme ||= req.scheme uri.scheme ||= req.scheme
uri.host ||= req.host uri.host ||= req.host
uri.port ||= req.port unless req.standard_port? uri.port ||= req.port unless req.standard_port?
@ -124,7 +124,7 @@ def path(params, request)
url_options[:script_name] = request.script_name url_options[:script_name] = request.script_name
end end
end end
ActionDispatch::Http::URL.url_for url_options ActionDispatch::Http::URL.url_for url_options
end end

@ -1,6 +1,5 @@
require 'action_dispatch/journey' require 'action_dispatch/journey'
require 'forwardable' require 'forwardable'
require 'thread_safe'
require 'active_support/concern' require 'active_support/concern'
require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/slice'
@ -21,64 +20,35 @@ class RouteSet
alias inspect to_s alias inspect to_s
class Dispatcher < Routing::Endpoint class Dispatcher < Routing::Endpoint
def initialize(defaults) def initialize(raise_on_name_error)
@defaults = defaults @raise_on_name_error = raise_on_name_error
@controller_class_names = ThreadSafe::Cache.new
end end
def dispatcher?; true; end def dispatcher?; true; end
def serve(req) def serve(req)
req.check_path_parameters!
params = req.path_parameters params = req.path_parameters
controller = controller_reference(req) do
prepare_params!(params)
# Just raise undefined constant errors if a controller was specified as default.
unless controller = controller(params, @defaults.key?(:controller))
return [404, {'X-Cascade' => 'pass'}, []] return [404, {'X-Cascade' => 'pass'}, []]
end end
dispatch(controller, params[:action], req)
dispatch(controller, params[:action], req.env)
end
def prepare_params!(params)
normalize_controller!(params)
merge_default_action!(params)
end
# If this is a default_controller (i.e. a controller specified by the user)
# we should raise an error in case it's not found, because it usually means
# a user error. However, if the controller was retrieved through a dynamic
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
def controller(params, default_controller=true)
if params && params.key?(:controller)
controller_param = params[:controller]
controller_reference(controller_param)
end
rescue NameError => e rescue NameError => e
raise ActionController::RoutingError, e.message, e.backtrace if default_controller if @raise_on_name_error
raise ActionController::RoutingError, e.message, e.backtrace
else
return [404, {'X-Cascade' => 'pass'}, []]
end
end
protected
def controller_reference(req, &block)
req.controller_class(&block)
end end
private private
def controller_reference(controller_param) def dispatch(controller, action, req)
const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" controller.action(action).call(req.env)
ActiveSupport::Dependencies.constantize(const_name)
end
def dispatch(controller, action, env)
controller.action(action).call(env)
end
def normalize_controller!(params)
params[:controller] = params[:controller].underscore if params.key?(:controller)
end
def merge_default_action!(params)
params[:action] ||= 'index'
end end
end end
@ -88,6 +58,7 @@ def merge_default_action!(params)
class NamedRouteCollection class NamedRouteCollection
include Enumerable include Enumerable
attr_reader :routes, :url_helpers_module, :path_helpers_module attr_reader :routes, :url_helpers_module, :path_helpers_module
private :routes
def initialize def initialize
@routes = {} @routes = {}
@ -142,6 +113,7 @@ def get(name)
end end
def key?(name) def key?(name)
return unless name
routes.key? name.to_sym routes.key? name.to_sym
end end
@ -267,9 +239,13 @@ def handle_positional_args(controller_options, inner_options, args, result, path
path_params -= controller_options.keys path_params -= controller_options.keys
path_params -= result.keys path_params -= result.keys
end end
path_params -= inner_options.keys inner_options.each_key do |key|
path_params.take(args.size).each do |param| path_params.delete(key)
result[param] = args.shift end
args.each_with_index do |arg, index|
param = path_params[index]
result[param] = arg if param
end end
end end
@ -309,7 +285,7 @@ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options attr_accessor :default_url_options, :dispatcher_class
attr_reader :env_key attr_reader :env_key
alias :routes :set alias :routes :set
@ -351,7 +327,8 @@ def initialize(config = DEFAULT_CONFIG)
@set = Journey::Routes.new @set = Journey::Routes.new
@router = Journey::Router.new @set @router = Journey::Router.new @set
@formatter = Journey::Formatter.new @set @formatter = Journey::Formatter.new self
@dispatcher_class = Routing::RouteSet::Dispatcher
end end
def relative_url_root def relative_url_root
@ -409,8 +386,8 @@ def clear!
@prepend.each { |blk| eval_block(blk) } @prepend.each { |blk| eval_block(blk) }
end end
def dispatcher(defaults) def dispatcher(raise_on_name_error)
Routing::RouteSet::Dispatcher.new(defaults) dispatcher_class.new(raise_on_name_error)
end end
module MountedHelpers module MountedHelpers
@ -508,7 +485,7 @@ def empty?
routes.empty? routes.empty?
end end
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) def add_route(mapping, path_ast, name, anchor)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
if name && named_routes[name] if name && named_routes[name]
@ -519,74 +496,17 @@ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil
"http://guides.rubyonrails.org/routing.html#restricting-the-routes-created" "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end end
path = conditions.delete :path_info route = @set.add_route(name, mapping)
ast = conditions.delete :parsed_path_info
required_defaults = conditions.delete :required_defaults
path = build_path(path, ast, requirements, anchor)
conditions = build_conditions(conditions)
route = @set.add_route(app, path, conditions, required_defaults, defaults, name)
named_routes[name] = route if name named_routes[name] = route if name
route route
end end
def build_path(path, ast, requirements, anchor)
strexp = Journey::Router::Strexp.new(
ast,
path,
requirements,
SEPARATORS,
anchor)
pattern = Journey::Path::Pattern.new(strexp)
builder = Journey::GTG::Builder.new pattern.spec
# Get all the symbol nodes followed by literals that are not the
# dummy node.
symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
builder.followpos(n).first.literal?
}
# Get all the symbol nodes preceded by literals.
symbols.concat pattern.spec.find_all(&:literal?).map { |n|
builder.followpos(n).first
}.find_all(&:symbol?)
symbols.each { |x|
x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
}
pattern
end
private :build_path
def build_conditions(current_conditions)
conditions = current_conditions.dup
# Rack-Mount requires that :request_method be a regular expression.
# :request_method represents the HTTP verb that matches this route.
#
# Here we munge values before they get sent on to rack-mount.
verbs = conditions[:request_method] || []
unless verbs.empty?
conditions[:request_method] = %r[^#{verbs.join('|')}$]
end
conditions.keep_if do |k, _|
request_class.public_method_defined?(k)
end
end
private :build_conditions
class Generator class Generator
PARAMETERIZE = lambda do |name, value| PARAMETERIZE = lambda do |name, value|
if name == :controller if name == :controller
value value
elsif value.is_a?(Array) else
value.map(&:to_param).join('/') value.to_param
elsif param = value.to_param
param
end end
end end
@ -594,8 +514,8 @@ class Generator
def initialize(named_route, options, recall, set) def initialize(named_route, options, recall, set)
@named_route = named_route @named_route = named_route
@options = options.dup @options = options
@recall = recall.dup @recall = recall
@set = set @set = set
normalize_recall! normalize_recall!
@ -617,7 +537,7 @@ def current_controller
def use_recall_for(key) def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key) if !named_route_exists? || segment_keys.include?(key)
@options[key] = @recall.delete(key) @options[key] = @recall[key]
end end
end end
end end
@ -671,12 +591,18 @@ def use_relative_controller!
# Remove leading slashes from controllers # Remove leading slashes from controllers
def normalize_controller! def normalize_controller!
@options[:controller] = controller.sub(%r{^/}, '') if controller if controller
if controller.start_with?("/".freeze)
@options[:controller] = controller[1..-1]
else
@options[:controller] = controller
end
end
end end
# Move 'index' action from options to recall # Move 'index' action from options to recall
def normalize_action! def normalize_action!
if @options[:action] == 'index' if @options[:action] == 'index'.freeze
@recall[:action] = @options.delete(:action) @recall[:action] = @options.delete(:action)
end end
end end
@ -803,14 +729,13 @@ def recognize_path(path, environment = {})
req.path_parameters = old_params.merge params req.path_parameters = old_params.merge params
app = route.app app = route.app
if app.matches?(req) && app.dispatcher? if app.matches?(req) && app.dispatcher?
dispatcher = app.app begin
req.controller_class
if dispatcher.controller(params, false) rescue NameError
dispatcher.prepare_params!(params)
return params
else
raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
end end
return req.path_parameters
end end
end end

@ -171,6 +171,10 @@ def url_for(options = nil)
route_name = options.delete :use_route route_name = options.delete :use_route
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options), _routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
route_name) route_name)
when ActionController::Parameters
route_name = options.delete :use_route
_routes.url_for(options.to_unsafe_h.symbolize_keys.
reverse_merge!(url_options), route_name)
when String when String
options options
when Symbol when Symbol

@ -3,6 +3,13 @@ module ActionDispatch
module Assertions module Assertions
# A small suite of assertions that test responses from \Rails applications. # A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions module ResponseAssertions
RESPONSE_PREDICATES = { # :nodoc:
success: :successful?,
missing: :not_found?,
redirect: :redirection?,
error: :server_error?,
}
# Asserts that the response is one of the following types: # Asserts that the response is one of the following types:
# #
# * <tt>:success</tt> - Status code was in the 200-299 range # * <tt>:success</tt> - Status code was in the 200-299 range
@ -20,11 +27,9 @@ module ResponseAssertions
# # assert that the response code was status code 401 (unauthorized) # # assert that the response code was status code 401 (unauthorized)
# assert_response 401 # assert_response 401
def assert_response(type, message = nil) def assert_response(type, message = nil)
message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
if Symbol === type if Symbol === type
if [:success, :missing, :redirect, :error].include?(type) if [:success, :missing, :redirect, :error].include?(type)
assert @response.send("#{type}?"), message assert_predicate @response, RESPONSE_PREDICATES[type], message
else else
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
if code.nil? if code.nil?

@ -86,8 +86,8 @@ def assert_generates(expected_path, options, defaults={}, extras={}, message=nil
end end
# Load routes.rb if it hasn't been loaded. # Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = @routes.generate_extras(options, defaults) generated_path, query_string_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject { |k, _| ! extra_keys.include? k } found_extras = options.reject { |k, _| ! query_string_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras) msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg) assert_equal(extras, found_extras, msg)
@ -165,7 +165,7 @@ def with_routing
# ROUTES TODO: These assertions should really work in an integration context # ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block) def method_missing(selector, *args, &block)
if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector) if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
@controller.send(selector, *args, &block) @controller.send(selector, *args, &block)
else else
super super

@ -325,7 +325,11 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
if path =~ %r{://} if path =~ %r{://}
location = URI.parse(path) location = URI.parse(path)
https! URI::HTTPS === location if location.scheme https! URI::HTTPS === location if location.scheme
host! "#{location.host}:#{location.port}" if location.host if url_host = location.host
default = Rack::Request::DEFAULT_PORTS[location.scheme]
url_host += ":#{location.port}" if default != location.port
host! url_host
end
path = location.query ? "#{location.path}?#{location.query}" : location.path path = location.query ? "#{location.path}?#{location.query}" : location.path
end end
@ -355,10 +359,10 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
# this modifies the passed request_env directly # this modifies the passed request_env directly
if headers.present? if headers.present?
Http::Headers.new(request_env).merge!(headers) Http::Headers.from_hash(request_env).merge!(headers)
end end
if env.present? if env.present?
Http::Headers.new(request_env).merge!(env) Http::Headers.from_hash(request_env).merge!(env)
end end
session = Rack::Test::Session.new(_mock_session) session = Rack::Test::Session.new(_mock_session)
@ -374,7 +378,7 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
@html_document = nil @html_document = nil
@url_options = nil @url_options = nil
@controller = session.last_request.env['action_controller.instance'] @controller = @request.controller_instance
response.status response.status
end end
@ -391,7 +395,7 @@ module Runner
attr_reader :app attr_reader :app
def before_setup def before_setup # :nodoc:
@app = nil @app = nil
@integration_session = nil @integration_session = nil
super super

@ -19,7 +19,7 @@ def flash
end end
def cookies def cookies
@cookie_jar ||= Cookies::CookieJar.build(@request.env, @request.host, @request.ssl?, @request.cookies) @cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies)
end end
def redirect_to_url def redirect_to_url

@ -16,9 +16,6 @@ def self.from_response(response)
# Was the URL not found? # Was the URL not found?
alias_method :missing?, :not_found? alias_method :missing?, :not_found?
# Were we redirected?
alias_method :redirect?, :redirection?
# Was there a server-side error? # Was there a server-side error?
alias_method :error?, :server_error? alias_method :error?, :server_error?
end end

@ -63,6 +63,10 @@ def env
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
SharedTestRoutes.draw do
get ':controller(/:action)'
end
module ActionDispatch module ActionDispatch
module SharedRoutes module SharedRoutes
def before_setup def before_setup
@ -70,35 +74,10 @@ def before_setup
super super
end end
end end
# Hold off drawing routes until all the possible controller classes
# have been loaded.
module DrawOnce
class << self
attr_accessor :drew
end
self.drew = false
def before_setup
super
return if DrawOnce.drew
SharedTestRoutes.draw do
get ':controller(/:action)'
end
ActionDispatch::IntegrationTest.app.routes.draw do
get ':controller(/:action)'
end
DrawOnce.drew = true
end
end
end end
module ActiveSupport module ActiveSupport
class TestCase class TestCase
include ActionDispatch::DrawOnce
if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
parallelize_me! parallelize_me!
end end
@ -119,29 +98,31 @@ def call(env)
end end
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
include ActionDispatch::SharedRoutes
def self.build_app(routes = nil) def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware| RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
middleware.use "ActionDispatch::DebugExceptions" middleware.use ActionDispatch::DebugExceptions
middleware.use "ActionDispatch::Callbacks" middleware.use ActionDispatch::Callbacks
middleware.use "ActionDispatch::ParamsParser" middleware.use ActionDispatch::ParamsParser
middleware.use "ActionDispatch::Cookies" middleware.use ActionDispatch::Cookies
middleware.use "ActionDispatch::Flash" middleware.use ActionDispatch::Flash
middleware.use "Rack::Head" middleware.use Rack::Head
yield(middleware) if block_given? yield(middleware) if block_given?
end end
end end
self.app = build_app self.app = build_app
app.routes.draw do
get ':controller(/:action)'
end
# Stub Rails dispatcher so it does not get controller references and # Stub Rails dispatcher so it does not get controller references and
# simply return the controller#action as Rack::Body. # simply return the controller#action as Rack::Body.
class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
protected protected
def controller_reference(controller_param) def controller_reference(controller_param)
controller_param controller_param.params[:controller]
end end
def dispatch(controller, action, env) def dispatch(controller, action, env)
@ -149,14 +130,10 @@ def dispatch(controller, action, env)
end end
end end
def self.stub_controllers def self.stub_controllers(config = nil)
old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher route_set = ActionDispatch::Routing::RouteSet.new(*[config].compact)
ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } route_set.dispatcher_class = StubDispatcher
ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher } yield route_set
yield ActionDispatch::Routing::RouteSet.new
ensure
ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
end end
def with_routing(&block) def with_routing(&block)

@ -7,7 +7,7 @@ class ResponseAssertionsTest < ActiveSupport::TestCase
include ResponseAssertions include ResponseAssertions
FakeResponse = Struct.new(:response_code) do FakeResponse = Struct.new(:response_code) do
[:success, :missing, :redirect, :error].each do |sym| [:successful, :not_found, :redirection, :server_error].each do |sym|
define_method("#{sym}?") do define_method("#{sym}?") do
sym == response_code sym == response_code
end end
@ -16,7 +16,7 @@ class ResponseAssertionsTest < ActiveSupport::TestCase
def test_assert_response_predicate_methods def test_assert_response_predicate_methods
[:success, :missing, :redirect, :error].each do |sym| [:success, :missing, :redirect, :error].each do |sym|
@response = FakeResponse.new sym @response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, '').to_sym
assert_response sym assert_response sym
assert_raises(Minitest::Assertion) { assert_raises(Minitest::Assertion) {

@ -43,12 +43,12 @@ def response599() head '599 Whoah!' end
def flash_me def flash_me
flash['hello'] = 'my name is inigo montoya...' flash['hello'] = 'my name is inigo montoya...'
render :text => "Inconceivable!" render plain: "Inconceivable!"
end end
def flash_me_naked def flash_me_naked
flash.clear flash.clear
render :text => "wow!" render plain: "wow!"
end end
def assign_this def assign_this
@ -57,30 +57,30 @@ def assign_this
end end
def render_based_on_parameters def render_based_on_parameters
render :text => "Mr. #{params[:name]}" render plain: "Mr. #{params[:name]}"
end end
def render_url def render_url
render :text => "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>" render html: "<div>#{url_for(action: 'flash_me', only_path: true)}</div>"
end end
def render_text_with_custom_content_type def render_text_with_custom_content_type
render :text => "Hello!", :content_type => Mime::RSS render body: "Hello!", content_type: Mime::RSS
end end
def session_stuffing def session_stuffing
session['xmas'] = 'turkey' session['xmas'] = 'turkey'
render :text => "ho ho ho" render plain: "ho ho ho"
end end
def raise_exception_on_get def raise_exception_on_get
raise "get" if request.get? raise "get" if request.get?
render :text => "request method: #{request.env['REQUEST_METHOD']}" render plain: "request method: #{request.env['REQUEST_METHOD']}"
end end
def raise_exception_on_post def raise_exception_on_post
raise "post" if request.post? raise "post" if request.post?
render :text => "request method: #{request.env['REQUEST_METHOD']}" render plain: "request method: #{request.env['REQUEST_METHOD']}"
end end
def render_file_absolute_path def render_file_absolute_path
@ -101,7 +101,7 @@ def index
end end
def show def show
render :text => "Boom", :status => 500 render plain: "Boom", status: 500
end end
end end
@ -318,7 +318,7 @@ def test_server_error_response_code
def test_missing_response_code def test_missing_response_code
process :response404 process :response404
assert @response.missing? assert @response.not_found?
end end
def test_client_error_response_code def test_client_error_response_code
@ -346,12 +346,12 @@ def test_redirection
def test_successful_response_code def test_successful_response_code
process :nothing process :nothing
assert @response.success? assert @response.successful?
end end
def test_response_object def test_response_object
process :nothing process :nothing
assert_kind_of ActionController::TestResponse, @response assert_kind_of ActionDispatch::TestResponse, @response
end end
def test_render_based_on_parameters def test_render_based_on_parameters

@ -7,12 +7,12 @@ class ConditionalGetApiController < ActionController::API
def one def one
if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123]) if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123])
render text: "Hi!" render plain: "Hi!"
end end
end end
def two def two
render text: "Hi!" render plain: "Hi!"
end end
private private

@ -7,7 +7,7 @@ class UsersController < ActionController::API
wrap_parameters :person, format: [:json] wrap_parameters :person, format: [:json]
def test def test
self.last_parameters = params.except(:controller, :action) self.last_parameters = params.except(:controller, :action).to_unsafe_h
head :ok head :ok
end end
end end

@ -53,7 +53,7 @@ class RecordIdentifierIncludedController < ActionController::Base
class ActionMissingController < ActionController::Base class ActionMissingController < ActionController::Base
def action_missing(action) def action_missing(action)
render :text => "Response for #{action}" render plain: "Response for #{action}"
end end
end end

@ -4,29 +4,29 @@ class OldContentTypeController < ActionController::Base
# :ported: # :ported:
def render_content_type_from_body def render_content_type_from_body
response.content_type = Mime::RSS response.content_type = Mime::RSS
render :text => "hello world!" render body: "hello world!"
end end
# :ported: # :ported:
def render_defaults def render_defaults
render :text => "hello world!" render body: "hello world!"
end end
# :ported: # :ported:
def render_content_type_from_render def render_content_type_from_render
render :text => "hello world!", :content_type => Mime::RSS render body: "hello world!", :content_type => Mime::RSS
end end
# :ported: # :ported:
def render_charset_from_body def render_charset_from_body
response.charset = "utf-16" response.charset = "utf-16"
render :text => "hello world!" render body: "hello world!"
end end
# :ported: # :ported:
def render_nil_charset_from_body def render_nil_charset_from_body
response.charset = nil response.charset = nil
render :text => "hello world!" render body: "hello world!"
end end
def render_default_for_erb def render_default_for_erb
@ -42,10 +42,10 @@ def render_change_for_builder
def render_default_content_types_for_respond_to def render_default_content_types_for_respond_to
respond_to do |format| respond_to do |format|
format.html { render :text => "hello world!" } format.html { render body: "hello world!" }
format.xml { render :action => "render_default_content_types_for_respond_to" } format.xml { render action: "render_default_content_types_for_respond_to" }
format.js { render :text => "hello world!" } format.js { render body: "hello world!" }
format.rss { render :text => "hello world!", :content_type => Mime::XML } format.rss { render body: "hello world!", content_type: Mime::XML }
end end
end end
end end
@ -64,14 +64,14 @@ def setup
def test_render_defaults def test_render_defaults
get :render_defaults get :render_defaults
assert_equal "utf-8", @response.charset assert_equal "utf-8", @response.charset
assert_equal Mime::HTML, @response.content_type assert_equal Mime::TEXT, @response.content_type
end end
def test_render_changed_charset_default def test_render_changed_charset_default
with_default_charset "utf-16" do with_default_charset "utf-16" do
get :render_defaults get :render_defaults
assert_equal "utf-16", @response.charset assert_equal "utf-16", @response.charset
assert_equal Mime::HTML, @response.content_type assert_equal Mime::TEXT, @response.content_type
end end
end end
@ -92,14 +92,14 @@ def test_content_type_from_render
# :ported: # :ported:
def test_charset_from_body def test_charset_from_body
get :render_charset_from_body get :render_charset_from_body
assert_equal Mime::HTML, @response.content_type assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-16", @response.charset assert_equal "utf-16", @response.charset
end end
# :ported: # :ported:
def test_nil_charset_from_body def test_nil_charset_from_body
get :render_nil_charset_from_body get :render_nil_charset_from_body
assert_equal Mime::HTML, @response.content_type assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-8", @response.charset, @response.headers.inspect assert_equal "utf-8", @response.charset, @response.headers.inspect
end end

@ -6,7 +6,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
after_action { I18n.locale = "en" } after_action { I18n.locale = "en" }
def target def target
render :text => "final response" render plain: "final response"
end end
def redirect def redirect

@ -40,7 +40,7 @@ class ChangingTheRequirementsController < TestController
before_action :ensure_login, :except => [:go_wild] before_action :ensure_login, :except => [:go_wild]
def go_wild def go_wild
render :text => "gobble" render plain: "gobble"
end end
end end
@ -51,7 +51,7 @@ class TestMultipleFiltersController < ActionController::Base
(1..3).each do |i| (1..3).each do |i|
define_method "fail_#{i}" do define_method "fail_#{i}" do
render :text => i.to_s render plain: i.to_s
end end
end end
@ -222,7 +222,7 @@ class SkipFilterUsingOnlyAndIf < ConditionalFilterController
skip_before_action :clean_up_tmp, only: :login, if: -> { true } skip_before_action :clean_up_tmp, only: :login, if: -> { true }
def login def login
render text: 'ok' render plain: 'ok'
end end
end end
@ -234,7 +234,7 @@ class SkipFilterUsingIfAndExcept < ConditionalFilterController
skip_before_action :clean_up_tmp, if: -> { true }, except: :login skip_before_action :clean_up_tmp, if: -> { true }, except: :login
def login def login
render text: 'ok' render plain: 'ok'
end end
end end
@ -258,11 +258,11 @@ class SkippingAndLimitedController < TestController
before_action :ensure_login, :only => :index before_action :ensure_login, :only => :index
def index def index
render :text => 'ok' render plain: 'ok'
end end
def public def public
render :text => 'ok' render plain: 'ok'
end end
end end
@ -272,7 +272,7 @@ class SkippingAndReorderingController < TestController
before_action :ensure_login before_action :ensure_login
def index def index
render :text => 'ok' render plain: 'ok'
end end
private private
@ -383,7 +383,7 @@ class AuditController < ActionController::Base
before_action(AuditFilter) before_action(AuditFilter)
def show def show
render :text => "hello" render plain: "hello"
end end
end end
@ -421,11 +421,11 @@ class OutOfOrder < StandardError; end
before_action :second, :only => :foo before_action :second, :only => :foo
def foo def foo
render :text => 'foo' render plain: 'foo'
end end
def bar def bar
render :text => 'bar' render plain: 'bar'
end end
protected protected
@ -442,7 +442,7 @@ class DynamicDispatchController < ActionController::Base
before_action :choose before_action :choose
%w(foo bar baz).each do |action| %w(foo bar baz).each do |action|
define_method(action) { render :text => action } define_method(action) { render plain: action }
end end
private private
@ -471,7 +471,7 @@ def between_before_all_and_after_all
@ran_filter << 'between_before_all_and_after_all' @ran_filter << 'between_before_all_and_after_all'
end end
def show def show
render :text => 'hello' render plain: 'hello'
end end
end end
@ -481,7 +481,7 @@ class RescuingAroundFilterWithBlock
def around(controller) def around(controller)
yield yield
rescue ErrorToRescue => ex rescue ErrorToRescue => ex
controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" controller.__send__ :render, plain: "I rescued this: #{ex.inspect}"
end end
end end
@ -819,7 +819,7 @@ def test_a_rescuing_around_action
response = test_process(RescuedController) response = test_process(RescuedController)
end end
assert response.success? assert response.successful?
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body) assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end end

@ -329,7 +329,7 @@ def with_test_route_set
@app = self.class.build_app(set) do |middleware| @app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey
middleware.use ActionDispatch::Flash middleware.use ActionDispatch::Flash
middleware.delete "ActionDispatch::ShowExceptions" middleware.delete ActionDispatch::ShowExceptions
end end
yield yield

@ -2,11 +2,11 @@
class ForceSSLController < ActionController::Base class ForceSSLController < ActionController::Base
def banana def banana
render :text => "monkey" render plain: "monkey"
end end
def cheeseburger def cheeseburger
render :text => "sikachu" render plain: "sikachu"
end end
end end
@ -26,7 +26,7 @@ class ForceSSLCustomOptions < ForceSSLController
force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice
def force_ssl_action def force_ssl_action
render :text => action_name render plain: action_name
end end
alias_method :redirect_host, :force_ssl_action alias_method :redirect_host, :force_ssl_action
@ -40,15 +40,15 @@ def force_ssl_action
alias_method :redirect_notice, :force_ssl_action alias_method :redirect_notice, :force_ssl_action
def use_flash def use_flash
render :text => flash[:message] render plain: flash[:message]
end end
def use_alert def use_alert
render :text => flash[:alert] render plain: flash[:alert]
end end
def use_notice def use_notice
render :text => flash[:notice] render plain: flash[:notice]
end end
end end
@ -85,10 +85,10 @@ def use_flash
class RedirectToSSL < ForceSSLController class RedirectToSSL < ForceSSLController
def banana def banana
force_ssl_redirect || render(:text => 'monkey') force_ssl_redirect || render(plain: 'monkey')
end end
def cheeseburger def cheeseburger
force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz') force_ssl_redirect('secure.cheeseburger.host') || render(plain: 'ihaz')
end end
end end

@ -151,7 +151,7 @@ def test_helper_for_acronym_controller
assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body
# #
# request = ActionController::TestRequest.new # request = ActionController::TestRequest.new
# response = ActionController::TestResponse.new # response = ActionDispatch::TestResponse.new
# request.action = 'test' # request.action = 'test'
# #
# assert_equal 'test: baz', Fun::PdfController.process(request, response).body # assert_equal 'test: baz', Fun::PdfController.process(request, response).body

@ -9,19 +9,19 @@ class DummyController < ActionController::Base
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
def index def index
render :text => "Hello Secret" render plain: "Hello Secret"
end end
def display def display
render :text => 'Definitely Maybe' if @logged_in render plain: 'Definitely Maybe' if @logged_in
end end
def show def show
render :text => 'Only for loooooong credentials' render plain: 'Only for loooooong credentials'
end end
def search def search
render :text => 'All inline' render plain: 'All inline'
end end
private private
@ -100,6 +100,14 @@ def test_encode_credentials_has_no_newline
assert_no_match(/\n/, result) assert_no_match(/\n/, result)
end end
test "succesful authentication with uppercase authorization scheme" do
@request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}"
get :index
assert_response :success
assert_equal 'Hello Secret', @response.body, 'Authentication failed when authorization scheme BASIC'
end
test "authentication request without credential" do test "authentication request without credential" do
get :display get :display

@ -10,11 +10,11 @@ class DummyDigestController < ActionController::Base
'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
def index def index
render :text => "Hello Secret" render plain: "Hello Secret"
end end
def display def display
render :text => 'Definitely Maybe' if @logged_in render plain: 'Definitely Maybe' if @logged_in
end end
private private

@ -7,15 +7,15 @@ class DummyController < ActionController::Base
before_action :authenticate_long_credentials, only: :show before_action :authenticate_long_credentials, only: :show
def index def index
render :text => "Hello Secret" render plain: "Hello Secret"
end end
def display def display
render :text => 'Definitely Maybe' render plain: 'Definitely Maybe'
end end
def show def show
render :text => 'Only for loooooong credentials' render plain: 'Only for loooooong credentials'
end end
private private

@ -359,28 +359,28 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
class IntegrationController < ActionController::Base class IntegrationController < ActionController::Base
def get def get
respond_to do |format| respond_to do |format|
format.html { render :text => "OK", :status => 200 } format.html { render plain: "OK", status: 200 }
format.js { render :text => "JS OK", :status => 200 } format.js { render plain: "JS OK", status: 200 }
format.xml { render :xml => "<root></root>", :status => 200 } format.xml { render :xml => "<root></root>", :status => 200 }
end end
end end
def get_with_params def get_with_params
render :text => "foo: #{params[:foo]}", :status => 200 render plain: "foo: #{params[:foo]}", status: 200
end end
def post def post
render :text => "Created", :status => 201 render plain: "Created", status: 201
end end
def method def method
render :text => "method: #{request.method.downcase}" render plain: "method: #{request.method.downcase}"
end end
def cookie_monster def cookie_monster
cookies["cookie_1"] = nil cookies["cookie_1"] = nil
cookies["cookie_3"] = "chocolate" cookies["cookie_3"] = "chocolate"
render :text => "Gone", :status => 410 render plain: "Gone", status: 410
end end
def set_cookie def set_cookie
@ -389,7 +389,7 @@ def set_cookie
end end
def get_cookie def get_cookie
render :text => cookies["foo"] render plain: cookies["foo"]
end end
def redirect def redirect
@ -755,12 +755,31 @@ def test_pass_env
assert_equal "http://test.com/", @request.env["HTTP_REFERER"] assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
end end
def test_ignores_common_ports_in_host
get "http://test.com"
assert_equal "test.com", @request.env["HTTP_HOST"]
get "https://test.com"
assert_equal "test.com", @request.env["HTTP_HOST"]
end
def test_keeps_uncommon_ports_in_host
get "http://test.com:123"
assert_equal "test.com:123", @request.env["HTTP_HOST"]
get "http://test.com:443"
assert_equal "test.com:443", @request.env["HTTP_HOST"]
get "https://test.com:80"
assert_equal "test.com:80", @request.env["HTTP_HOST"]
end
end end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base class TestController < ActionController::Base
def index def index
render :text => "index" render plain: "index"
end end
end end
@ -847,7 +866,7 @@ def app
class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base class TestController < ActionController::Base
def post def post
render :text => "Created", :status => 201 render plain: "Created", status: 201
end end
end end
@ -880,15 +899,15 @@ def app
class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
class FooController < ActionController::Base class FooController < ActionController::Base
def index def index
render :text => "foo#index" render plain: "foo#index"
end end
def show def show
render :text => "foo#show" render plain: "foo#show"
end end
def edit def edit
render :text => "foo#show" render plain: "foo#show"
end end
end end
@ -898,7 +917,7 @@ def default_url_options
end end
def index def index
render :text => "foo#index" render plain: "foo#index"
end end
end end

@ -1,5 +1,5 @@
require 'abstract_unit' require 'abstract_unit'
require 'active_support/concurrency/latch' require 'concurrent/atomics'
Thread.abort_on_exception = true Thread.abort_on_exception = true
module ActionController module ActionController
@ -125,7 +125,7 @@ def set_cookie
end end
def render_text def render_text
render :text => 'zomg' render plain: 'zomg'
end end
def default_header def default_header
@ -145,7 +145,7 @@ def blocking_stream
response.headers['Content-Type'] = 'text/event-stream' response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word| %w{ hello world }.each do |word|
response.stream.write word response.stream.write word
latch.await latch.wait
end end
response.stream.close response.stream.close
end end
@ -162,7 +162,7 @@ def thread_locals
end end
def with_stale def with_stale
render text: 'stale' if stale?(etag: "123", template: false) render plain: 'stale' if stale?(etag: "123", template: false)
end end
def exception_in_view def exception_in_view
@ -212,7 +212,7 @@ def overfill_buffer_and_die
# .. plus one more, because the #each frees up a slot: # .. plus one more, because the #each frees up a slot:
response.stream.write '.' response.stream.write '.'
latch.release latch.count_down
# This write will block, and eventually raise # This write will block, and eventually raise
response.stream.write 'x' response.stream.write 'x'
@ -233,7 +233,7 @@ def ignore_client_disconnect
end end
logger.info 'Work complete' logger.info 'Work complete'
latch.release latch.count_down
end end
end end
@ -278,7 +278,7 @@ def test_write_to_stream
def test_async_stream def test_async_stream
rubinius_skip "https://github.com/rubinius/rubinius/issues/2934" rubinius_skip "https://github.com/rubinius/rubinius/issues/2934"
@controller.latch = ActiveSupport::Concurrency::Latch.new @controller.latch = Concurrent::CountDownLatch.new
parts = ['hello', 'world'] parts = ['hello', 'world']
@controller.request = @request @controller.request = @request
@ -289,8 +289,8 @@ def test_async_stream
resp.stream.each do |part| resp.stream.each do |part|
assert_equal parts.shift, part assert_equal parts.shift, part
ol = @controller.latch ol = @controller.latch
@controller.latch = ActiveSupport::Concurrency::Latch.new @controller.latch = Concurrent::CountDownLatch.new
ol.release ol.count_down
end end
} }
@ -300,23 +300,23 @@ def test_async_stream
end end
def test_abort_with_full_buffer def test_abort_with_full_buffer
@controller.latch = ActiveSupport::Concurrency::Latch.new @controller.latch = Concurrent::CountDownLatch.new
@request.parameters[:format] = 'plain' @request.parameters[:format] = 'plain'
@controller.request = @request @controller.request = @request
@controller.response = @response @controller.response = @response
got_error = ActiveSupport::Concurrency::Latch.new got_error = Concurrent::CountDownLatch.new
@response.stream.on_error do @response.stream.on_error do
ActionController::Base.logger.warn 'Error while streaming' ActionController::Base.logger.warn 'Error while streaming'
got_error.release got_error.count_down
end end
t = Thread.new(@response) { |resp| t = Thread.new(@response) { |resp|
resp.await_commit resp.await_commit
_, _, body = resp.to_a _, _, body = resp.to_a
body.each do |part| body.each do |part|
@controller.latch.await @controller.latch.wait
body.close body.close
break break
end end
@ -325,13 +325,13 @@ def test_abort_with_full_buffer
capture_log_output do |output| capture_log_output do |output|
@controller.process :overfill_buffer_and_die @controller.process :overfill_buffer_and_die
t.join t.join
got_error.await got_error.wait
assert_match 'Error while streaming', output.rewind && output.read assert_match 'Error while streaming', output.rewind && output.read
end end
end end
def test_ignore_client_disconnect def test_ignore_client_disconnect
@controller.latch = ActiveSupport::Concurrency::Latch.new @controller.latch = Concurrent::CountDownLatch.new
@controller.request = @request @controller.request = @request
@controller.response = @response @controller.response = @response
@ -349,7 +349,7 @@ def test_ignore_client_disconnect
@controller.process :ignore_client_disconnect @controller.process :ignore_client_disconnect
t.join t.join
Timeout.timeout(3) do Timeout.timeout(3) do
@controller.latch.await @controller.latch.wait
end end
assert_match 'Work complete', output.rewind && output.read assert_match 'Work complete', output.rewind && output.read
end end

@ -13,41 +13,41 @@ class RespondToController < ActionController::Base
def html_xml_or_rss def html_xml_or_rss
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.xml { render :text => "XML" } type.xml { render body: "XML" }
type.rss { render :text => "RSS" } type.rss { render body: "RSS" }
type.all { render :text => "Nothing" } type.all { render body: "Nothing" }
end end
end end
def js_or_html def js_or_html
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.js { render :text => "JS" } type.js { render body: "JS" }
type.all { render :text => "Nothing" } type.all { render body: "Nothing" }
end end
end end
def json_or_yaml def json_or_yaml
respond_to do |type| respond_to do |type|
type.json { render :text => "JSON" } type.json { render body: "JSON" }
type.yaml { render :text => "YAML" } type.yaml { render body: "YAML" }
end end
end end
def html_or_xml def html_or_xml
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.xml { render :text => "XML" } type.xml { render body: "XML" }
type.all { render :text => "Nothing" } type.all { render body: "Nothing" }
end end
end end
def json_xml_or_html def json_xml_or_html
respond_to do |type| respond_to do |type|
type.json { render :text => 'JSON' } type.json { render body: 'JSON' }
type.xml { render :xml => 'XML' } type.xml { render :xml => 'XML' }
type.html { render :text => 'HTML' } type.html { render body: 'HTML' }
end end
end end
@ -56,14 +56,14 @@ def forced_xml
request.format = :xml request.format = :xml
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.xml { render :text => "XML" } type.xml { render body: "XML" }
end end
end end
def just_xml def just_xml
respond_to do |type| respond_to do |type|
type.xml { render :text => "XML" } type.xml { render body: "XML" }
end end
end end
@ -81,52 +81,52 @@ def using_defaults_with_type_list
def using_defaults_with_all def using_defaults_with_all
respond_to do |type| respond_to do |type|
type.html type.html
type.all{ render text: "ALL" } type.all{ render body: "ALL" }
end end
end end
def made_for_content_type def made_for_content_type
respond_to do |type| respond_to do |type|
type.rss { render :text => "RSS" } type.rss { render body: "RSS" }
type.atom { render :text => "ATOM" } type.atom { render body: "ATOM" }
type.all { render :text => "Nothing" } type.all { render body: "Nothing" }
end end
end end
def custom_type_handling def custom_type_handling
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.custom("application/crazy-xml") { render :text => "Crazy XML" } type.custom("application/crazy-xml") { render body: "Crazy XML" }
type.all { render :text => "Nothing" } type.all { render body: "Nothing" }
end end
end end
def custom_constant_handling def custom_constant_handling
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.mobile { render :text => "Mobile" } type.mobile { render body: "Mobile" }
end end
end end
def custom_constant_handling_without_block def custom_constant_handling_without_block
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.mobile type.mobile
end end
end end
def handle_any def handle_any
respond_to do |type| respond_to do |type|
type.html { render :text => "HTML" } type.html { render body: "HTML" }
type.any(:js, :xml) { render :text => "Either JS or XML" } type.any(:js, :xml) { render body: "Either JS or XML" }
end end
end end
def handle_any_any def handle_any_any
respond_to do |type| respond_to do |type|
type.html { render :text => 'HTML' } type.html { render body: 'HTML' }
type.any { render :text => 'Whatever you ask for, I got it' } type.any { render body: 'Whatever you ask for, I got it' }
end end
end end
@ -167,15 +167,15 @@ def variant_with_format_and_custom_render
request.variant = :mobile request.variant = :mobile
respond_to do |type| respond_to do |type|
type.html { render text: "mobile" } type.html { render body: "mobile" }
end end
end end
def multiple_variants_for_format def multiple_variants_for_format
respond_to do |type| respond_to do |type|
type.html do |html| type.html do |html|
html.tablet { render text: "tablet" } html.tablet { render body: "tablet" }
html.phone { render text: "phone" } html.phone { render body: "phone" }
end end
end end
end end
@ -183,7 +183,7 @@ def multiple_variants_for_format
def variant_plus_none_for_format def variant_plus_none_for_format
respond_to do |format| respond_to do |format|
format.html do |variant| format.html do |variant|
variant.phone { render text: "phone" } variant.phone { render body: "phone" }
variant.none variant.none
end end
end end
@ -191,9 +191,9 @@ def variant_plus_none_for_format
def variant_inline_syntax def variant_inline_syntax
respond_to do |format| respond_to do |format|
format.js { render text: "js" } format.js { render body: "js" }
format.html.none { render text: "none" } format.html.none { render body: "none" }
format.html.phone { render text: "phone" } format.html.phone { render body: "phone" }
end end
end end
@ -208,8 +208,8 @@ def variant_inline_syntax_without_block
def variant_any def variant_any
respond_to do |format| respond_to do |format|
format.html do |variant| format.html do |variant|
variant.any(:tablet, :phablet){ render text: "any" } variant.any(:tablet, :phablet){ render body: "any" }
variant.phone { render text: "phone" } variant.phone { render body: "phone" }
end end
end end
end end
@ -217,23 +217,23 @@ def variant_any
def variant_any_any def variant_any_any
respond_to do |format| respond_to do |format|
format.html do |variant| format.html do |variant|
variant.any { render text: "any" } variant.any { render body: "any" }
variant.phone { render text: "phone" } variant.phone { render body: "phone" }
end end
end end
end end
def variant_inline_any def variant_inline_any
respond_to do |format| respond_to do |format|
format.html.any(:tablet, :phablet){ render text: "any" } format.html.any(:tablet, :phablet){ render body: "any" }
format.html.phone { render text: "phone" } format.html.phone { render body: "phone" }
end end
end end
def variant_inline_any_any def variant_inline_any_any
respond_to do |format| respond_to do |format|
format.html.phone { render text: "phone" } format.html.phone { render body: "phone" }
format.html.any { render text: "any" } format.html.any { render body: "any" }
end end
end end
@ -246,16 +246,16 @@ def variant_any_implicit_render
def variant_any_with_none def variant_any_with_none
respond_to do |format| respond_to do |format|
format.html.any(:none, :phone){ render text: "none or phone" } format.html.any(:none, :phone){ render body: "none or phone" }
end end
end end
def format_any_variant_any def format_any_variant_any
respond_to do |format| respond_to do |format|
format.html { render text: "HTML" } format.html { render body: "HTML" }
format.any(:js, :xml) do |variant| format.any(:js, :xml) do |variant|
variant.phone{ render text: "phone" } variant.phone{ render body: "phone" }
variant.any(:tablet, :phablet){ render text: "tablet" } variant.any(:tablet, :phablet){ render body: "tablet" }
end end
end end
end end
@ -780,7 +780,7 @@ def test_variant_negotiation_without_block
class RespondToWithBlockOnDefaultRenderController < ActionController::Base class RespondToWithBlockOnDefaultRenderController < ActionController::Base
def show def show
default_render do default_render do
render text: 'default_render yielded' render body: 'default_render yielded'
end end
end end
end end
@ -794,6 +794,6 @@ def setup
def test_default_render_uses_block_when_no_template_exists def test_default_render_uses_block_when_no_template_exists
get :show get :show
assert_equal "default_render yielded", @response.body assert_equal "default_render yielded", @response.body
assert_equal "text/html", @response.content_type assert_equal "text/plain", @response.content_type
end end
end end

@ -6,7 +6,7 @@ class SimpleController < ActionController::Base
before_action :authenticate before_action :authenticate
def index def index
render :text => "success" render body: "success"
end end
def modify_response_body def modify_response_body
@ -22,7 +22,7 @@ def modify_response_headers
end end
def show_actions def show_actions
render :text => "actions: #{action_methods.to_a.sort.join(', ')}" render body: "actions: #{action_methods.to_a.sort.join(', ')}"
end end
protected protected
@ -51,7 +51,7 @@ class BaseTest < Rack::TestCase
assert_body "success" assert_body "success"
assert_status 200 assert_status 200
assert_content_type "text/html; charset=utf-8" assert_content_type "text/plain; charset=utf-8"
end end
# :api: plugin # :api: plugin

@ -9,7 +9,7 @@ class BasicController < ActionController::Base
)] )]
def all def all
render :text => self.formats.inspect render plain: self.formats.inspect
end end
end end

@ -3,16 +3,16 @@
module ContentType module ContentType
class BaseController < ActionController::Base class BaseController < ActionController::Base
def index def index
render :text => "Hello world!" render body: "Hello world!"
end end
def set_on_response_obj def set_on_response_obj
response.content_type = Mime::RSS response.content_type = Mime::RSS
render :text => "Hello world!" render body: "Hello world!"
end end
def set_on_render def set_on_render
render :text => "Hello world!", :content_type => Mime::RSS render body: "Hello world!", content_type: Mime::RSS
end end
end end
@ -30,17 +30,17 @@ class ImpliedController < ActionController::Base
class CharsetController < ActionController::Base class CharsetController < ActionController::Base
def set_on_response_obj def set_on_response_obj
response.charset = "utf-16" response.charset = "utf-16"
render :text => "Hello world!" render body: "Hello world!"
end end
def set_as_nil_on_response_obj def set_as_nil_on_response_obj
response.charset = nil response.charset = nil
render :text => "Hello world!" render body: "Hello world!"
end end
end end
class ExplicitContentTypeTest < Rack::TestCase class ExplicitContentTypeTest < Rack::TestCase
test "default response is HTML and UTF8" do test "default response is text/plain and UTF8" do
with_routing do |set| with_routing do |set|
set.draw do set.draw do
get ':controller', :action => 'index' get ':controller', :action => 'index'
@ -49,7 +49,7 @@ class ExplicitContentTypeTest < Rack::TestCase
get "/content_type/base" get "/content_type/base"
assert_body "Hello world!" assert_body "Hello world!"
assert_header "Content-Type", "text/html; charset=utf-8" assert_header "Content-Type", "text/plain; charset=utf-8"
end end
end end
@ -99,14 +99,14 @@ class ExplicitCharsetTest < Rack::TestCase
get "/content_type/charset/set_on_response_obj" get "/content_type/charset/set_on_response_obj"
assert_body "Hello world!" assert_body "Hello world!"
assert_header "Content-Type", "text/html; charset=utf-16" assert_header "Content-Type", "text/plain; charset=utf-16"
end end
test "setting the charset of the response as nil directly on the response object" do test "setting the charset of the response as nil directly on the response object" do
get "/content_type/charset/set_as_nil_on_response_obj" get "/content_type/charset/set_as_nil_on_response_obj"
assert_body "Hello world!" assert_body "Hello world!"
assert_header "Content-Type", "text/html; charset=utf-8" assert_header "Content-Type", "text/plain; charset=utf-8"
end end
end end
end end

Some files were not shown because too many files have changed in this diff Show More