aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Gemfile7
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md2
-rw-r--r--RELEASING_RAILS.rdoc31
-rw-r--r--Rakefile6
-rw-r--r--actionmailer/README.rdoc2
-rw-r--r--actionmailer/lib/action_mailer/base.rb8
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionmailer/test/delivery_methods_test.rb29
-rw-r--r--actionpack/CHANGELOG.md78
-rw-r--r--actionpack/RUNNING_UNIT_TESTS.rdoc6
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb9
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb12
-rw-r--r--actionpack/lib/action_controller/metal/live.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb28
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb45
-rw-r--r--actionpack/lib/action_dispatch.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb117
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb27
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/json_serializer.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb14
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb13
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/test/controller/flash_hash_test.rb10
-rw-r--r--actionpack/test/controller/flash_test.rb8
-rw-r--r--actionpack/test/controller/live_stream_test.rb18
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb25
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb175
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb190
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb168
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb36
-rw-r--r--actionpack/test/controller/url_for_test.rb3
-rw-r--r--actionpack/test/dispatch/cookies_test.rb157
-rw-r--r--actionpack/test/dispatch/rack_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb14
-rw-r--r--actionpack/test/dispatch/response_test.rb26
-rw-r--r--actionpack/test/dispatch/routing_test.rb75
-rw-r--r--actionpack/test/dispatch/static_test.rb9
-rw-r--r--actionview/CHANGELOG.md23
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb19
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb7
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb10
-rw-r--r--actionview/lib/action_view/rendering.rb9
-rw-r--r--actionview/lib/action_view/template.rb1
-rw-r--r--actionview/lib/action_view/template/html.rb34
-rw-r--r--actionview/lib/action_view/template/text.rb2
-rw-r--r--actionview/lib/action_view/version.rb2
-rw-r--r--actionview/test/fixtures/customers/_customer.xml.erb1
-rw-r--r--actionview/test/template/date_helper_test.rb10
-rw-r--r--actionview/test/template/html_test.rb17
-rw-r--r--actionview/test/template/number_helper_test.rb39
-rw-r--r--actionview/test/template/render_test.rb12
-rw-r--r--actionview/test/template/text_test.rb17
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb19
-rw-r--r--activerecord/CHANGELOG.md108
-rw-r--r--activerecord/lib/active_record/associations.rb1
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb68
-rw-r--r--activerecord/lib/active_record/associations/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb89
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb8
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/migration.rb12
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb7
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb10
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/lib/active_record/sanitization.rb3
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb48
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb39
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb196
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb10
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb5
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/autosave_association_test.rb54
-rw-r--r--activerecord/test/cases/connection_pool_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb8
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb28
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb6
-rw-r--r--activerecord/test/cases/reaper_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb9
-rw-r--r--activerecord/test/cases/sanitize_test.rb5
-rw-r--r--activerecord/test/models/electron.rb2
-rw-r--r--activerecord/test/models/molecule.rb2
-rw-r--r--activesupport/CHANGELOG.md23
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb12
-rw-r--r--activesupport/lib/active_support/dependencies.rb11
-rw-r--r--activesupport/lib/active_support/inflections.rb6
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb8
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb32
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb5
-rw-r--r--activesupport/test/dependencies_test.rb12
-rw-r--r--activesupport/test/time_zone_test.rb26
-rw-r--r--guides/source/4_1_release_notes.md24
-rw-r--r--guides/source/_welcome.html.erb2
-rw-r--r--guides/source/action_controller_overview.md50
-rw-r--r--guides/source/active_record_querying.md24
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_support_core_extensions.md2
-rw-r--r--guides/source/api_documentation_guidelines.md9
-rw-r--r--guides/source/asset_pipeline.md11
-rw-r--r--guides/source/configuring.md5
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md2
-rw-r--r--guides/source/getting_started.md96
-rw-r--r--guides/source/initialization.md4
-rw-r--r--guides/source/layout.html.erb2
-rw-r--r--guides/source/layouts_and_rendering.md44
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/security.md2
-rw-r--r--guides/source/testing.md16
-rw-r--r--guides/source/upgrading_ruby_on_rails.md110
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--railties/CHANGELOG.md11
-rw-r--r--railties/lib/rails/application.rb5
-rw-r--r--railties/lib/rails/application/bootstrap.rb6
-rw-r--r--railties/lib/rails/generators/app_base.rb98
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.text.erb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb5
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/test/application/configuration_test.rb23
-rw-r--r--railties/test/generators/app_generator_test.rb69
-rw-r--r--railties/test/generators/generator_test.rb1
-rw-r--r--railties/test/generators/mailer_generator_test.rb8
-rw-r--r--version.rb2
154 files changed, 2498 insertions, 863 deletions
diff --git a/.travis.yml b/.travis.yml
index 3ddaf86fb2..4233b136a8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,7 @@ rvm:
- 1.9.3
- 2.0.0
- 2.1.0
- - rbx
+ - rbx-2
- jruby
env:
- "GEM=railties"
@@ -17,7 +17,7 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
- - rvm: rbx
+ - rvm: rbx-2
- rvm: jruby
fast_finish: true
notifications:
diff --git a/Gemfile b/Gemfile
index 88f3686e95..753b1d423a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,7 +9,7 @@ gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.1.2'
-gem 'jquery-rails', '~> 2.2.0'
+gem 'jquery-rails', '~> 3.1.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
@@ -76,11 +76,6 @@ platforms :jruby do
end
end
-platforms :rbx do
- gem 'psych', '~> 2.0'
- gem 'rubysl', '~> 2.0'
-end
-
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED']
platforms :ruby do
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 78dae579e8..ee00187eb3 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.1.0.beta1
+4.1.0.beta2
diff --git a/README.md b/README.md
index cf93453d3a..7f079536d2 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the
## Code Status
-* [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails)
+* [![Build Status](https://travis-ci.org/rails/rails.png?branch=master)](https://travis-ci.org/rails/rails)
## License
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 664505f60d..9b06d0d162 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -203,34 +203,3 @@ There are two simple steps for fixing the CI:
2. Fix it
Repeat these steps until the CI is green.
-
-=== Manually trigger docs generation
-
-We have a post-receive hook in GitHub that calls the docs server on pushes.
-It triggers generation and publication of edge docs, updates the contrib app,
-and generates and publishes stable docs if a new stable tag is detected.
-
-The hook unfortunately is not invoked by tag pushing, so once the new stable
-tag has been pushed to origin, please run
-
- rake publish_docs
-
-You should see something like this:
-
- Rails master hook tasks scheduled:
-
- * updates the local checkout
- * updates Rails Contributors
- * generates and publishes edge docs
-
- If a new stable tag is detected it also
-
- * generates and publishes stable docs
-
- This needs typically a few minutes.
-
-Note you do not need to specify the tag, the docs server figures it out.
-
-Also, don't worry if you call that multiple times or the hook is triggered
-again by some immediate regular push, if the scripts are running new calls
-are just queued (in a queue of size 1).
diff --git a/Rakefile b/Rakefile
index 07d44fc94b..fadebc241f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -74,7 +74,6 @@ task :update_versions do
end
end
-#
# We have a webhook configured in GitHub that gets invoked after pushes.
# This hook triggers the following tasks:
#
@@ -84,11 +83,6 @@ end
# * if there's a new stable tag, generates and publishes stable docs
#
# Everything is automated and you do NOT need to run this task normally.
-#
-# We publish a new version by tagging, and pushing a tag does not trigger
-# that webhook. Stable docs would be updated by any subsequent regular
-# push, but if you want that to happen right away just run this.
-#
desc 'Publishes docs, run this AFTER a new stable tag has been pushed'
task :publish_docs do
Net::HTTP.new('api.rubyonrails.org', 8080).start do |http|
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index c3dcd3c3e4..e425282fa8 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -74,7 +74,7 @@ Or you can just chain the methods together like:
== Setting defaults
-It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers email messages have, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
Note that every value you set with this method will get overwritten if you use the same key in your mailer method.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 388f694590..18a41ba7a4 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -724,11 +724,11 @@ module ActionMailer
# format.html
# end
#
- # You can even render text directly without using a template:
+ # You can even render plain text directly without using a template:
#
# mail(to: 'mikel@test.lindsaar.net') do |format|
- # format.text { render text: "Hello Mikel!" }
- # format.html { render text: "<h1>Hello Mikel!</h1>" }
+ # format.text { render plain: "Hello Mikel!" }
+ # format.html { render html: "<h1>Hello Mikel!</h1>".html_safe }
# end
#
# Which will render a +multipart/alternative+ email with +text/plain+ and
@@ -764,7 +764,7 @@ module ActionMailer
m.charset = charset = headers[:charset]
# 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
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index fde0e86f5b..33a9faa7e7 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -20,7 +20,7 @@ module ActionMailer
interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
end
- # Register am Interceptor which will be called before mail is previewed.
+ # Register an Interceptor which will be called before mail is previewed.
# Either a class or a string can be passed in as the Interceptor. If a
# string is passed in it will be <tt>constantize</tt>d.
def register_preview_interceptor(interceptor)
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 46eb763c26..60732c593b 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,7 +1,7 @@
module ActionMailer
# Returns the version of the currently loaded ActionMailer as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta1"
+ Gem::Version.new "4.1.0.beta2"
end
module VERSION #:nodoc:
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 20412c7bb2..609903620b 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -38,8 +38,10 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
end
test "default sendmail settings" do
- settings = {location: '/usr/sbin/sendmail',
- arguments: '-i -t'}
+ settings = {
+ location: '/usr/sbin/sendmail',
+ arguments: '-i -t'
+ }
assert_equal settings, ActionMailer::Base.sendmail_settings
end
end
@@ -138,13 +140,15 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
test "default delivery options can be overridden per mail instance" do
- settings = { address: "localhost",
- port: 25,
- domain: 'localhost.localdomain',
- user_name: nil,
- password: nil,
- authentication: nil,
- enable_starttls_auto: true }
+ settings = {
+ address: "localhost",
+ port: 25,
+ domain: 'localhost.localdomain',
+ user_name: nil,
+ password: nil,
+ authentication: nil,
+ enable_starttls_auto: true
+ }
assert_equal settings, ActionMailer::Base.smtp_settings
overridden_options = {user_name: "overridden", password: "somethingobtuse"}
mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options)
@@ -164,6 +168,13 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
end
+ test "undefined delivery methods raises errors" do
+ DeliveryMailer.delivery_method = nil
+ assert_raise RuntimeError do
+ DeliveryMailer.welcome.deliver
+ end
+ end
+
test "does not perform deliveries if requested" do
DeliveryMailer.perform_deliveries = false
DeliveryMailer.deliveries.clear
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index b63fb4a9b6..b05aa21f95 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,22 +1,74 @@
-* Do not discard query parameters that form a hash with the same root key as
- the `wrapper_key` for a request using `wrap_parameters`.
+* Introduce `render :html` as an option to render HTML content with a content
+ type of `text/html`. This rendering option calls `ERB::Util.html_escape`
+ internally to escape unsafe HTML string, so you will have to mark your
+ string as html safe if you have any HTML tag in it.
- *Josh Jordan*
+ Please see #12374 for more detail.
+
+ *Prem Sichanugrist*
+
+* Introduce `render :plain` as an option to render content with a content type
+ of `text/plain`. This is the preferred option if you are planning to render
+ a plain text content.
+
+ Please see #12374 for more detail.
+
+ *Prem Sichanugrist*
+
+* Introduce `render :body` as an option for sending a raw content back to
+ browser. Note that this rendering option will unset the default content type
+ and does not include "Content-Type" header back in the response.
+
+ You should only use this option if you are expecting the "Content-Type"
+ header to not be set. More information on "Content-Type" header can be found
+ on RFC 2616, section 7.2.1.
+
+ Please see #12374 for more detail.
+
+ *Prem Sichanugrist*
+
+* Set stream status to 500 (or 400 on BadRequest) when an error is thrown
+ before commiting.
+
+ Fixes #12552.
-* Add `:serializer` option for `config.session_store :cookie_store`. This
- changes default serializer when using `:cookie_store`.
+ *Kevin Casey*
- It is possible to pass:
+* Add new config option `config.action_dispatch.cookies_serializer` for
+ specifying a serializer for the signed and encrypted cookie jars.
- * `:json` which is a secure wrapper on JSON using `JSON.parse` and
- `JSON.generate` methods with quirks mode;
- * `:marshal` which is a wrapper on Marshal;
- * serializer class with `load` and `dump` methods defined.
+ The possible values are:
- For new apps `:json` option is added by default and :marshal is used
- when no option is specified.
+ * `:json` - serialize cookie values with `JSON`
+ * `:marshal` - serialize cookie values with `Marshal`
+ * `:hybrid` - transparently migrate existing `Marshal` cookie values to `JSON`
- *Łukasz Sarnacki*, *Matt Aimonetti*
+ For new apps `:json` option is added by default and `:marshal` is used
+ when no option is specified to maintain backwards compatibility.
+
+ *Łukasz Sarnacki*, *Matt Aimonetti*, *Guillermo Iguaran*, *Godfrey Chan*, *Rafael Mendonça França*
+
+* `FlashHash` now behaves like a `HashWithIndifferentAccess`.
+
+ *Guillermo Iguaran*
+
+* Set the `:shallow_path` scope option as each scope is generated rather than
+ waiting until the `shallow` option is set. Also make the behavior of the
+ `:shallow` resource option consistent with the behavior of the `shallow` method.
+
+ Fixes #12498.
+
+ *Andrew White*, *Aleksi Aalto*
+
+* Properly require `action_view` in `AbstractController::Rendering` to prevent
+ uninitialized constant error for `ENCODING_FLAG`.
+
+ *Philipe Fatio*
+
+* Do not discard query parameters that form a hash with the same root key as
+ the `wrapper_key` for a request using `wrap_parameters`.
+
+ *Josh Jordan*
* Ensure that `request.filtered_parameters` is reset between calls to `process`
in `ActionController::TestCase`.
diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc
index 08767ae133..ad1448f61b 100644
--- a/actionpack/RUNNING_UNIT_TESTS.rdoc
+++ b/actionpack/RUNNING_UNIT_TESTS.rdoc
@@ -1,10 +1,10 @@
== Running with Rake
The easiest way to run the unit tests is through Rake. The default task runs
-the entire test suite for all classes. For more information, checkout the
-full array of rake tasks with "rake -T"
+the entire test suite for all classes. For more information, check out the
+full array of rake tasks with "rake -T".
-Rake can be found at http://rake.rubyforge.org
+Rake can be found at http://rake.rubyforge.org.
== Running by hand
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 7be61d94c9..9d10140ed2 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
+require 'action_view'
require 'action_view/view_paths'
require 'set'
@@ -22,7 +23,7 @@ module AbstractController
def render(*args, &block)
options = _normalize_render(*args, &block)
self.response_body = render_to_body(options)
- _process_format(rendered_format) if rendered_format
+ _process_format(rendered_format, options) if rendered_format
self.response_body
end
@@ -97,7 +98,7 @@ module AbstractController
# Process the rendered format.
# :api: private
- def _process_format(format)
+ def _process_format(format, options = {})
end
# Normalize args and options.
@@ -105,7 +106,9 @@ module AbstractController
def _normalize_render(*args, &block)
options = _normalize_args(*args, &block)
#TODO: remove defined? when we restore AP <=> AV dependency
- options[:variant] = request.variant if defined?(request) && request.variant.present?
+ if defined?(request) && request && request.variant.present?
+ options[:variant] = request.variant
+ end
_normalize_options(options)
options
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index c0f10da23a..e6fe6b0b00 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -45,7 +45,7 @@ module ActionController
#
# def server_ip
# location = request.env["SERVER_ADDR"]
- # render text: "This server hosted at #{location}"
+ # render plain: "This server hosted at #{location}"
# end
#
# == Parameters
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 75c4d3ef99..1abd8d3a33 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -96,7 +96,7 @@ module ActionController #:nodoc:
end
# Sends the given binary data to the browser. This method is similar to
- # <tt>render text: data</tt>, but also allows you to specify whether
+ # <tt>render plain: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
# the apparent file name, and other things.
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 158d552ec7..1acc19d74b 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -11,11 +11,11 @@ module ActionController
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
#
# def index
- # render text: "Everyone can see me!"
+ # render plain: "Everyone can see me!"
# end
#
# def edit
- # render text: "I'm only accessible if you know the password"
+ # render plain: "I'm only accessible if you know the password"
# end
# end
#
@@ -127,11 +127,11 @@ module ActionController
# before_action :authenticate, except: [:index]
#
# def index
- # render text: "Everyone can see me!"
+ # render plain: "Everyone can see me!"
# end
#
# def edit
- # render text: "I'm only accessible if you know the password"
+ # render plain: "I'm only accessible if you know the password"
# end
#
# private
@@ -321,11 +321,11 @@ module ActionController
# before_action :authenticate, except: [ :index ]
#
# def index
- # render text: "Everyone can see me!"
+ # render plain: "Everyone can see me!"
# end
#
# def edit
- # render text: "I'm only accessible if you know the password"
+ # render plain: "I'm only accessible if you know the password"
# end
#
# private
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 33014b97ca..fdf4ef293d 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -205,6 +205,8 @@ module ActionController
begin
super(name)
rescue => e
+ @_response.status = 500 unless @_response.committed?
+ @_response.status = 400 if e.class == ActionController::BadRequest
begin
@_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
@_response.stream.call_on_error
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index d5e08b7034..1974bbf529 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -236,6 +236,18 @@ module ActionController #:nodoc:
# end
# end
#
+ # You can also set an array of variants:
+ #
+ # request.variant = [:tablet, :phone]
+ #
+ # which will work similarly to formats and MIME types negotiation. If there will be no
+ # :tablet variant declared, :phone variant will be picked:
+ #
+ # respond_to do |format|
+ # format.html.none
+ # format.html.phone # this gets rendered
+ # end
+ #
# Be sure to check the documentation of +respond_with+ and
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
def respond_to(*mimes, &block)
@@ -488,7 +500,7 @@ module ActionController #:nodoc:
response
else # `format.html{ |variant| variant.phone }` - variant block syntax
variant_collector = VariantCollector.new(@variant)
- response.call(variant_collector) #call format block with variants collector
+ response.call(variant_collector) # call format block with variants collector
variant_collector.variant
end
end
@@ -519,15 +531,15 @@ module ActionController #:nodoc:
end
def variant
- key = if @variant.nil?
- :none
- elsif @variants.has_key?(@variant)
- @variant
+ if @variant.nil?
+ @variants[:none] || @variants[:any]
+ elsif (@variants.keys & @variant).any?
+ @variant.each do |v|
+ return @variants[v] if @variants.key?(v)
+ end
else
- :any
+ @variants[:any]
end
-
- @variants[key]
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index bdf6e88699..e1bee9e60c 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -5,8 +5,8 @@ module ActionController
module RackDelegation
extend ActiveSupport::Concern
- delegate :headers, :status=, :location=, :content_type=,
- :status, :location, :content_type, :to => "@_response"
+ delegate :headers, :status=, :location=, :content_type=, :no_content_type=,
+ :status, :location, :content_type, :no_content_type, :to => "@_response"
def dispatch(action, request)
set_response!(request)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 5c48b4ab98..3c4ef596c7 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -2,6 +2,8 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
+ RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
@@ -27,14 +29,29 @@ module ActionController
end
def render_to_body(options = {})
- super || options[:text].presence || ' '
+ super || _render_in_priorities(options) || ' '
end
private
- def _process_format(format)
+ def _render_in_priorities(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ return options[format] if options.key?(format)
+ end
+
+ nil
+ end
+
+ def _process_format(format, options = {})
super
- self.content_type ||= format.to_s
+
+ if options[:body]
+ self.headers.delete "Content-Type"
+ elsif options[:plain]
+ self.content_type = Mime::TEXT
+ else
+ self.content_type ||= format.to_s
+ end
end
# Normalize arguments by catching blocks and setting them on :update.
@@ -46,12 +63,14 @@ module ActionController
# Normalize both text and status options.
def _normalize_options(options) #:nodoc:
- if options.key?(:text) && options[:text].respond_to?(:to_text)
- options[:text] = options[:text].to_text
+ _normalize_text(options)
+
+ if options[:html]
+ options[:html] = ERB::Util.html_escape(options[:html])
end
- if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
- options[:text] = " "
+ if options.delete(:nothing) || _any_render_format_is_nil?(options)
+ options[:body] = " "
end
if options[:status]
@@ -61,6 +80,18 @@ module ActionController
super
end
+ def _normalize_text(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ if options.key?(format) && options[format].respond_to?(:to_text)
+ options[format] = options[format].to_text
+ end
+ end
+ end
+
+ def _any_render_format_is_nil?(options)
+ RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
+ end
+
# Process controller specific options, as status, content-type and location.
def _process_options(options) #:nodoc:
status, content_type, location = options.values_at(:status, :content_type, :location)
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 9b26845190..3dd2e2a45c 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -74,7 +74,6 @@ module ActionDispatch
autoload :MimeNegotiation
autoload :Parameters
autoload :ParameterFilter
- autoload :FilterParameters
autoload :Upload
autoload :UploadedFile, 'action_dispatch/http/upload'
autoload :URL
@@ -85,8 +84,6 @@ module ActionDispatch
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
- autoload :JsonSerializer, 'action_dispatch/middleware/session/json_serializer'
- autoload :MarshalSerializer, 'action_dispatch/middleware/session/marshal_serializer'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index c33ba201e1..b803ce8b6f 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -68,10 +68,12 @@ module ActionDispatch
# Sets the \variant for template.
def variant=(variant)
- if variant.is_a? Symbol
+ if variant.is_a?(Symbol)
+ @variant = [variant]
+ elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
@variant = variant
else
- raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
"For security reasons, never directly set the variant to a user-provided value, " \
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index bc13ee00f1..f14ca1ea44 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -63,6 +63,8 @@ module ActionDispatch # :nodoc:
# content you're giving them, so we need to send that along.
attr_accessor :charset
+ attr_accessor :no_content_type # :nodoc:
+
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
@@ -303,8 +305,17 @@ module ActionDispatch # :nodoc:
!@sending_file && @charset != false
end
+ def remove_content_type!
+ headers.delete CONTENT_TYPE
+ end
+
def rack_response(status, header)
- assign_default_content_type_and_charset!(header)
+ if no_content_type
+ remove_content_type!
+ else
+ assign_default_content_type_and_charset!(header)
+ end
+
handle_conditional_get!
header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
@@ -313,7 +324,7 @@ module ActionDispatch # :nodoc:
header.delete CONTENT_TYPE
[status, header, []]
else
- [status, header, self]
+ [status, header, Rack::BodyProxy.new(self){}]
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 23d0ecd529..18e64704f6 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -23,15 +23,15 @@ module ActionDispatch
# # This cookie will be deleted when the user's browser is closed.
# cookies[:user_name] = "david"
#
- # # Assign an array of values to a cookie.
- # cookies[:lat_lon] = [47.68, -122.37]
+ # # Cookie values are String based. Other data types need to be serialized.
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
#
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
+ # # The cookie is signed by your app's `secrets.secret_key_base` value.
+ # # It can be read using the signed method `cookies.signed[:name]`
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -42,10 +42,10 @@ module ActionDispatch
#
# Examples of reading:
#
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
- # cookies.signed[:login] # => "XJ-122"
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
+ # cookies.signed[:login] # => "XJ-122"
#
# Example for deleting:
#
@@ -63,7 +63,7 @@ module ActionDispatch
#
# The option symbols for setting cookies are:
#
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
+ # * <tt>:value</tt> - The cookie's value.
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
# of the application.
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
@@ -89,7 +89,7 @@ module ActionDispatch
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
- SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -181,7 +181,7 @@ module ActionDispatch
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@legacy_verifier.verify(signed_message).tap do |value|
- self[name] = value
+ self[name] = { value: value }
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
@@ -212,7 +212,7 @@ module ActionDispatch
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?,
- session_serializer: env[SESSION_SERIALIZER]
+ serializer: env[COOKIES_SERIALIZER]
}
end
@@ -374,28 +374,89 @@ module ActionDispatch
end
end
+ class JsonSerializer
+ def self.load(value)
+ JSON.parse(value, quirks_mode: true)
+ end
+
+ def self.dump(value)
+ JSON.generate(value, quirks_mode: true)
+ end
+ end
+
+ # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
+ # allows us to handle the (de)serialization step within the cookie jar,
+ # which gives us the opportunity to detect and migrate legacy cookies.
+ class NullSerializer
+ def self.load(value)
+ value
+ end
+
+ def self.dump(value)
+ value
+ end
+ end
+
+ module SerializedCookieJars
+ MARSHAL_SIGNATURE = "\x04\x08".freeze
+
+ protected
+ def needs_migration?(value)
+ @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
+ end
+
+ def serialize(name, value)
+ serializer.dump(value)
+ end
+
+ def deserialize(name, value)
+ if value
+ if needs_migration?(value)
+ Marshal.load(value).tap do |v|
+ self[name] = { value: v }
+ end
+ else
+ serializer.load(value)
+ end
+ end
+ end
+
+ def serializer
+ serializer = @options[:serializer] || :marshal
+ case serializer
+ when :marshal
+ Marshal
+ when :json, :hybrid
+ JsonSerializer
+ else
+ serializer
+ end
+ end
+ end
+
class SignedCookieJar #:nodoc:
include ChainedCookieJars
+ include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
- @verifier = ActiveSupport::MessageVerifier.new(secret)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
end
def [](name)
if signed_message = @parent_jar[name]
- verify(signed_message)
+ deserialize name, verify(signed_message)
end
end
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
- options[:value] = @verifier.generate(options[:value])
+ options[:value] = @verifier.generate(serialize(name, options[:value]))
else
- options = { :value => @verifier.generate(options) }
+ options = { :value => @verifier.generate(serialize(name, options)) }
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@@ -419,13 +480,14 @@ module ActionDispatch
def [](name)
if signed_message = @parent_jar[name]
- verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message)
end
end
end
class EncryptedCookieJar #:nodoc:
include ChainedCookieJars
+ include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::LegacyKeyGenerator === key_generator
@@ -437,12 +499,12 @@ module ActionDispatch
@options = options
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, serializer: serializer)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
end
def [](name)
if encrypted_message = @parent_jar[name]
- decrypt_and_verify(encrypted_message)
+ deserialize name, decrypt_and_verify(encrypted_message)
end
end
@@ -452,7 +514,8 @@ module ActionDispatch
else
options = { :value => options }
end
- options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[name] = options
@@ -464,18 +527,6 @@ module ActionDispatch
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
nil
end
-
- def serializer
- serializer = @options[:session_serializer] || :marshal
- case serializer
- when :marshal
- ActionDispatch::Session::MarshalSerializer
- when :json
- ActionDispatch::Session::JsonSerializer
- else
- serializer
- end
- end
end
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
@@ -487,7 +538,7 @@ module ActionDispatch
def [](name)
if encrypted_or_signed_message = @parent_jar[name]
- decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
+ deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 89003e7a5e..4821d2a899 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/keys'
+
module ActionDispatch
class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -50,13 +52,14 @@ module ActionDispatch
end
def []=(k, v)
+ k = k.to_s
@flash[k] = v
@flash.discard(k)
v
end
def [](k)
- @flash[k]
+ @flash[k.to_s]
end
# Convenience accessor for <tt>flash.now[:alert]=</tt>.
@@ -92,8 +95,8 @@ module ActionDispatch
end
def initialize(flashes = {}, discard = []) #:nodoc:
- @discard = Set.new(discard)
- @flashes = flashes
+ @discard = Set.new(stringify_array(discard))
+ @flashes = flashes.stringify_keys
@now = nil
end
@@ -106,17 +109,18 @@ module ActionDispatch
end
def []=(k, v)
+ k = k.to_s
@discard.delete k
@flashes[k] = v
end
def [](k)
- @flashes[k]
+ @flashes[k.to_s]
end
def update(h) #:nodoc:
- @discard.subtract h.keys
- @flashes.update h
+ @discard.subtract stringify_array(h.keys)
+ @flashes.update h.stringify_keys
self
end
@@ -129,6 +133,7 @@ module ActionDispatch
end
def delete(key)
+ key = key.to_s
@discard.delete key
@flashes.delete key
self
@@ -155,7 +160,7 @@ module ActionDispatch
def replace(h) #:nodoc:
@discard.clear
- @flashes.replace h
+ @flashes.replace h.stringify_keys
self
end
@@ -186,6 +191,7 @@ module ActionDispatch
# flash.keep # keeps the entire flash
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
def keep(k = nil)
+ k = k.to_s if k
@discard.subtract Array(k || keys)
k ? self[k] : self
end
@@ -195,6 +201,7 @@ module ActionDispatch
# flash.discard # discard the entire flash at the end of the current action
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
def discard(k = nil)
+ k = k.to_s if k
@discard.merge Array(k || keys)
k ? self[k] : self
end
@@ -231,6 +238,12 @@ module ActionDispatch
def now_is_loaded?
@now
end
+
+ def stringify_array(array)
+ array.map do |item|
+ item.kind_of?(Symbol) ? item.to_s : item
+ end
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 57bc6d5cd0..c1df518b14 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -47,12 +47,12 @@ module ActionDispatch
# clients (like WAP devices), or behind proxies that set headers in an
# incorrect or confusing way (like AWS ELB).
#
- # The +custom_trusted+ argument can take a regex, which will be used
+ # The +custom_proxies+ argument can take a regex, which will be used
# instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
# to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
# middle (or at the beginning) of the X-Forwarded-For list, with your proxy
# servers after it. If your proxies aren't removed, pass them in via the
- # +custom_trusted+ parameter. That way, the middleware will ignore those
+ # +custom_proxies+ parameter. That way, the middleware will ignore those
# IP addresses, and return the one that you want.
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
diff --git a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
deleted file mode 100644
index d341853f7a..0000000000
--- a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionDispatch
- module Session
- class JsonSerializer
- def self.load(value)
- JSON.parse(value, quirks_mode: true)
- end
-
- def self.dump(value)
- JSON.generate(value, quirks_mode: true)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb
deleted file mode 100644
index 26622f682d..0000000000
--- a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module ActionDispatch
- module Session
- class MarshalSerializer
- def self.load(value)
- Marshal.load(value)
- end
-
- def self.dump(value)
- Marshal.dump(value)
- end
- end
- end
-end
-
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index d5eb770cb1..0b762aa9a4 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -707,6 +707,10 @@ module ActionDispatch
options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
+ unless shallow?
+ options[:shallow_path] = options[:path] if args.any?
+ end
+
if options[:constraints].is_a?(Hash)
defaults = options[:constraints].select do
|k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
@@ -1369,7 +1373,7 @@ module ActionDispatch
end
def shallow
- scope(:shallow => true, :shallow_path => @scope[:path]) do
+ scope(:shallow => true) do
yield
end
end
@@ -1490,6 +1494,13 @@ module ActionDispatch
return true
end
+ if options.delete(:shallow)
+ shallow do
+ send(method, resources.pop, options, &block)
+ end
+ return true
+ end
+
if resource_scope?
nested { send(method, resources.pop, options, &block) }
return true
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index a51f6a434a..8da3069c8b 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,7 +1,7 @@
module ActionPack
# Returns the version of the currently loaded ActionPack as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta1"
+ Gem::Version.new "4.1.0.beta2"
end
module VERSION #:nodoc:
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index 5490d9394b..50b36a0567 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -67,6 +67,16 @@ module ActionDispatch
assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
end
+ def test_from_session_value_on_json_serializer
+ decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
+ session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
+ hash = Flash::FlashHash.from_session_value(session['flash'])
+
+ assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
+ assert_equal "hey you", hash[:message]
+ assert_equal "hey you", hash["message"]
+ end
+
def test_empty?
assert @hash.empty?
@hash['zomg'] = 'bears'
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 9ceab91e42..25a4857eba 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -175,13 +175,13 @@ class FlashTest < ActionController::TestCase
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existent key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existent key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 0a431270b5..fb6a750089 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -156,6 +156,14 @@ module ActionController
raise 'An exception occurred...'
end
+ def exception_in_controller
+ raise 'Exception in controller'
+ end
+
+ def bad_request_error
+ raise ActionController::BadRequest
+ end
+
def exception_in_exception_callback
response.headers['Content-Type'] = 'text/event-stream'
response.stream.on_error do
@@ -275,6 +283,16 @@ module ActionController
end
end
+ def test_exception_in_controller_before_streaming
+ response = get :exception_in_controller, format: 'text/event-stream'
+ assert_equal 500, response.status
+ end
+
+ def test_bad_request_in_controller_before_streaming
+ response = get :bad_request_error, format: 'text/event-stream'
+ assert_equal 400, response.status
+ end
+
def test_exceptions_raised_handling_exceptions
capture_log_output do |output|
get :exception_in_exception_callback, format: 'text/event-stream'
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 84e4936f31..499c62cc35 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -671,6 +671,10 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_variant_any_any
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
@request.variant = :phone
get :variant_any_any
assert_equal "text/html", @response.content_type
@@ -740,4 +744,25 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/javascript", @response.content_type
assert_equal "tablet", @response.body
end
+
+ def test_variant_negotiation_inline_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_block_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_plus_none_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_without_block
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
end
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
new file mode 100644
index 0000000000..a7e4f87bd9
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -0,0 +1,175 @@
+require 'abstract_unit'
+
+module RenderBody
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render body: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render body: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render body: "hello david"
+ end
+
+ def custom_code
+ render body: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render body: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render body: nil
+ end
+
+ def with_nil_and_status
+ render body: nil, status: 403
+ end
+
+ def with_false
+ render body: false
+ end
+
+ def with_layout_true
+ render body: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render body: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render body: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render body: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render body: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderBodyTest < Rack::TestCase
+ test "rendering body from a minimal controller" do
+ get "/render_body/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering body from an action with default options renders the body with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body from an action with default options renders the body without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body, while also providing a custom status code" do
+ get "/render_body/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering body with nil returns an empty body padded for Safari" do
+ get "/render_body/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering body with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_body/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering body with false returns the string 'false'" do
+ get "/render_body/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering body with layout: true" do
+ get "/render_body/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering body with layout: 'greetings'" do
+ get "/render_body/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering body with layout: false" do
+ get "/render_body/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering body with layout: nil" do
+ get "/render_body/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with no content type" do
+ get "/render_body/minimal/index"
+
+ assert_header_no_content_type
+ end
+
+ test "rendering from normal controller returns response with no content type" do
+ get "/render_body/simple/index"
+
+ assert_header_no_content_type
+ end
+
+ def assert_header_no_content_type
+ assert_not response.headers.has_key?("Content-Type"),
+ %(Expect response not to have Content-Type header, got "#{response.headers["Content-Type"]}")
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
new file mode 100644
index 0000000000..bfe0271df7
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -0,0 +1,190 @@
+require 'abstract_unit'
+
+module RenderHtml
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render html: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render html: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render html: "hello david"
+ end
+
+ def custom_code
+ render html: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render html: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render html: nil
+ end
+
+ def with_nil_and_status
+ render html: nil, status: 403
+ end
+
+ def with_false
+ render html: false
+ end
+
+ def with_layout_true
+ render html: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render html: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render html: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render html: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render html: "hello world", layout: "ivar"
+ end
+
+ def with_unsafe_html_tag
+ render html: "<p>hello world</p>", layout: nil
+ end
+
+ def with_safe_html_tag
+ render html: "<p>hello world</p>".html_safe, layout: nil
+ end
+ end
+
+ class RenderHtmlTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_html/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_html/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body padded for Safari" do
+ get "/render_html/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_html/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_html/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_html/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_html/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_html/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_html/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering html should escape the string if it is not html safe" do
+ get "/render_html/with_layout/with_unsafe_html_tag"
+
+ assert_body "&lt;p&gt;hello world&lt;/p&gt;"
+ assert_status 200
+ end
+
+ test "rendering html should not escape the string if it is html safe" do
+ get "/render_html/with_layout/with_safe_html_tag"
+
+ assert_body "<p>hello world</p>"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/html content type" do
+ get "/render_html/minimal/index"
+ assert_content_type "text/html"
+ end
+
+ test "rendering from normal controller returns response with text/html content type" do
+ get "/render_html/simple/index"
+ assert_content_type "text/html; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
new file mode 100644
index 0000000000..dba2e9f13e
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -0,0 +1,168 @@
+require 'abstract_unit'
+
+module RenderPlain
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render plain: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render plain: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.text.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.text.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.text.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render plain: "hello david"
+ end
+
+ def custom_code
+ render plain: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render plain: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render plain: nil
+ end
+
+ def with_nil_and_status
+ render plain: nil, status: 403
+ end
+
+ def with_false
+ render plain: false
+ end
+
+ def with_layout_true
+ render plain: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render plain: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render plain: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render plain: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render plain: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderPlainTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_plain/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_plain/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body padded for Safari" do
+ get "/render_plain/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_plain/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_plain/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_plain/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_plain/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_plain/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_plain/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/plain content type" do
+ get "/render_plain/minimal/index"
+ assert_content_type "text/plain"
+ end
+
+ test "rendering from normal controller returns response with text/plain content type" do
+ get "/render_plain/simple/index"
+ assert_content_type "text/plain; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 2a253799f3..abb81d7e71 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -14,7 +14,7 @@ module RenderText
self.view_paths = [ActionView::FixtureResolver.new]
def index
- render :text => "hello david"
+ render text: "hello david"
end
end
@@ -26,48 +26,48 @@ module RenderText
)]
def index
- render :text => "hello david"
+ render text: "hello david"
end
def custom_code
- render :text => "hello world", :status => 404
+ render text: "hello world", status: 404
end
def with_custom_code_as_string
- render :text => "hello world", :status => "404 Not Found"
+ render text: "hello world", status: "404 Not Found"
end
def with_nil
- render :text => nil
+ render text: nil
end
def with_nil_and_status
- render :text => nil, :status => 403
+ render text: nil, status: 403
end
def with_false
- render :text => false
+ render text: false
end
def with_layout_true
- render :text => "hello world", :layout => true
+ render text: "hello world", layout: true
end
def with_layout_false
- render :text => "hello world", :layout => false
+ render text: "hello world", layout: false
end
def with_layout_nil
- render :text => "hello world", :layout => nil
+ render text: "hello world", layout: nil
end
def with_custom_layout
- render :text => "hello world", :layout => "greetings"
+ render text: "hello world", layout: "greetings"
end
def with_ivar_in_layout
@ivar = "hello world"
- render :text => "hello world", :layout => "ivar"
+ render text: "hello world", layout: "ivar"
end
end
@@ -80,7 +80,7 @@ module RenderText
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', :action => 'index' }
+ set.draw { get ':controller', action: 'index' }
get "/render_text/simple"
assert_body "hello david"
@@ -90,7 +90,7 @@ module RenderText
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', :action => 'index' }
+ set.draw { get ':controller', action: 'index' }
get "/render_text/with_layout"
@@ -127,28 +127,28 @@ module RenderText
assert_status 200
end
- test "rendering text with :layout => true" do
+ test "rendering text with layout: true" do
get "/render_text/with_layout/with_layout_true"
assert_body "hello world, I'm here!"
assert_status 200
end
- test "rendering text with :layout => 'greetings'" do
+ test "rendering text with layout: 'greetings'" do
get "/render_text/with_layout/with_custom_layout"
assert_body "hello world, I wish thee well."
assert_status 200
end
- test "rendering text with :layout => false" do
+ test "rendering text with layout: false" do
get "/render_text/with_layout/with_layout_false"
assert_body "hello world"
assert_status 200
end
- test "rendering text with :layout => nil" do
+ test "rendering text with layout: nil" do
get "/render_text/with_layout/with_layout_nil"
assert_body "hello world"
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index d2b4952759..a8035e5bd7 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -204,9 +204,6 @@ module AbstractController
end
def test_relative_url_root_is_respected
- # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This
- # should probably come from routes.
-
add_host!
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir')
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 6101acdc25..ba7aaa338d 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -11,6 +11,16 @@ require 'active_support/key_generator'
require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
+ class CustomSerializer
+ def self.load(value)
+ value.to_s + " and loaded"
+ end
+
+ def self.dump(value)
+ value.to_s + " was dumped"
+ end
+ end
+
class TestController < ActionController::Base
def authenticate
cookies["user_name"] = "david"
@@ -359,9 +369,72 @@ class CookiesTest < ActionController::TestCase
assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
end
- def test_signed_cookie
+ def test_signed_cookie_using_default_serializer
get :set_signed_cookie
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_signed_cookie
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal '45 was dumped and loaded', cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45)
+ @request.headers["Cookie"] = "user_id=#{marshal_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies['user_id'])
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+ json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45)
+ @request.headers["Cookie"] = "user_id=#{json_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ assert_nil @response.cookies["user_id"]
end
def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
@@ -369,43 +442,87 @@ class CookiesTest < ActionController::TestCase
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
end
- def test_encrypted_cookie
+ def test_encrypted_cookie_using_default_serializer
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal 'bar', cookies[:foo]
- assert_raises TypeError do
+ assert_raise TypeError do
cookies.signed[:foo]
end
assert_equal 'bar', cookies.encrypted[:foo]
end
- class CustomJsonSerializer
- def self.load(value)
- JSON.load(value) + " and loaded"
- end
-
- def self.dump(value)
- JSON.dump(value + " was dumped")
- end
- end
-
- def test_encrypted_cookie_using_serializer_object
- @request.env["action_dispatch.session_serializer"] = CustomJsonSerializer
+ def test_encrypted_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
get :set_encrypted_cookie
- assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
end
def test_encrypted_cookie_using_json_serializer
- @request.env["action_dispatch.session_serializer"] = :json
+ @request.env["action_dispatch.cookies_serializer"] = :json
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal 'bar', cookies[:foo]
- assert_raises TypeError do
+ assert_raises ::JSON::ParserError do
cookies.signed[:foo]
end
assert_equal 'bar', cookies.encrypted[:foo]
end
+ def test_encrypted_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_encrypted_cookie
+ assert_not_equal 'bar', cookies.encrypted[:foo]
+ assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{json_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ assert_nil @response.cookies["foo"]
+ end
+
def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
@@ -721,8 +838,6 @@ class CookiesTest < ActionController::TestCase
assert_equal "dhh", cookies['user_name']
end
-
-
def test_setting_request_cookies_is_indifferent_access
cookies.clear
cookies[:user_name] = "andrew"
diff --git a/actionpack/test/dispatch/rack_test.rb b/actionpack/test/dispatch/rack_test.rb
index 42067854ee..ef1964fd19 100644
--- a/actionpack/test/dispatch/rack_test.rb
+++ b/actionpack/test/dispatch/rack_test.rb
@@ -119,7 +119,7 @@ class RackRequestTest < BaseRackTest
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
- test "cgi environment variables" do
+ test "CGI environment variables" do
assert_equal "Basic", @request.auth_type
assert_equal 0, @request.content_length
assert_equal nil, @request.content_mime_type
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f79fe47897..40e32cb4d3 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -846,8 +846,20 @@ class RequestTest < ActiveSupport::TestCase
test "setting variant" do
request = stub_request
+
request.variant = :mobile
- assert_equal :mobile, request.variant
+ assert_equal [:mobile], request.variant
+
+ request.variant = [:phone, :tablet]
+ assert_equal [:phone, :tablet], request.variant
+
+ assert_raise ArgumentError do
+ request.variant = [:phone, "tablet"]
+ end
+
+ assert_raise ArgumentError do
+ request.variant = "yolo"
+ end
end
test "setting variant with non symbol value" do
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 4501ea095c..1360ede3f8 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -217,6 +217,32 @@ class ResponseTest < ActiveSupport::TestCase
assert_not @response.respond_to?(:method_missing)
assert @response.respond_to?(:method_missing, true)
end
+
+ test "can be destructured into status, headers and an enumerable body" do
+ response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
+ status, headers, body = response
+
+ assert_equal 404, status
+ assert_equal({ 'Content-Type' => 'text/plain' }, headers)
+ assert_equal ['Not Found'], body.each.to_a
+ end
+
+ test "[response].flatten does not recurse infinitely" do
+ Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
+ status, headers, body = [@response].flatten
+ assert_equal @response.status, status
+ assert_equal @response.headers, headers
+ assert_equal @response.body, body.each.to_a.join
+ end
+ end
+
+ test "does not add default content-type if Content-Type is none" do
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.no_content_type = true
+ }
+
+ assert_not resp.headers.has_key?('Content-Type')
+ end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 26821bdb56..1fa2cc6cf2 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1889,6 +1889,65 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'notes#destroy', @response.body
end
+ def test_shallow_option_nested_resources_within_scope
+ draw do
+ scope '/hello' do
+ resources :notes, :shallow => true do
+ resources :trackbacks
+ end
+ end
+ end
+
+ get '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#index', @response.body
+ assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
+
+ get '/hello/notes/1/edit'
+ assert_equal 'notes#edit', @response.body
+ assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
+
+ get '/hello/notes/1/trackbacks/new'
+ assert_equal 'trackbacks#new', @response.body
+ assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
+
+ get '/hello/trackbacks/1'
+ assert_equal 'trackbacks#show', @response.body
+ assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
+
+ get '/hello/trackbacks/1/edit'
+ assert_equal 'trackbacks#edit', @response.body
+ assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
+
+ put '/hello/trackbacks/1'
+ assert_equal 'trackbacks#update', @response.body
+
+ post '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#create', @response.body
+
+ delete '/hello/trackbacks/1'
+ assert_equal 'trackbacks#destroy', @response.body
+
+ get '/hello/notes'
+ assert_equal 'notes#index', @response.body
+
+ post '/hello/notes'
+ assert_equal 'notes#create', @response.body
+
+ get '/hello/notes/new'
+ assert_equal 'notes#new', @response.body
+ assert_equal '/hello/notes/new', new_note_path
+
+ get '/hello/notes/1'
+ assert_equal 'notes#show', @response.body
+ assert_equal '/hello/notes/1', note_path(:id => 1)
+
+ put '/hello/notes/1'
+ assert_equal 'notes#update', @response.body
+
+ delete '/hello/notes/1'
+ assert_equal 'notes#destroy', @response.body
+ end
+
def test_custom_resource_routes_are_scoped
draw do
resources :customers do
@@ -2958,6 +3017,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/photos/1', photo_path('1')
end
+ def test_shallow_path_inside_namespace_is_not_added_twice
+ draw do
+ namespace :admin do
+ shallow do
+ resources :posts do
+ resources :comments
+ end
+ end
+ end
+ end
+
+ get '/admin/posts/1/comments'
+ assert_equal 'admin/comments#index', @response.body
+ assert_equal '/admin/posts/1/comments', admin_post_comments_path('1')
+ end
+
private
def draw(&block)
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 5bd1806b21..afdda70748 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -136,10 +136,15 @@ module StaticTests
def with_static_file(file)
path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file
- File.open(path, "wb+") { |f| f.write(file) }
+ begin
+ File.open(path, "wb+") { |f| f.write(file) }
+ rescue Errno::EPROTO
+ skip "Couldn't create a file #{path}"
+ end
+
yield file
ensure
- File.delete(path)
+ File.delete(path) if File.exist? path
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 30dbc20f18..c05ed10263 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,22 @@
+* Added `:plain`, `:html` and `:body` option for `render` method. Please see
+ Action Pack's release note for more detail.
+
+ *Prem Sichanugrist*
+
+* Date select helpers accept a format string for the months selector via the
+ new option `:month_format_string`.
+
+ When rendered, the format string gets passed keys `:number` (integer), and
+ `:name` (string), in order to be able to interpolate them as in
+
+ '%{name} (%<number>02d)'
+
+ for example.
+
+ This option is motivated by #13618.
+
+ *Xavier Noria*
+
* Added `config.action_view.raise_on_missing_translations` to define whether an
error should be raised for missing translations.
@@ -26,6 +45,10 @@
*Kuldeep Aggarwal*
+* Escape format, negative_format and units options of number helpers
+
+ Fixes: CVE-2014-0081
+
* A Cycle object should accept an array and cycle through it as it would with a set of
comma-separated objects.
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 3d091c4a00..698f0ca31c 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -169,6 +169,9 @@ module ActionView
# "2 - February" instead of "February").
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
# Note: You can also use Rails' i18n functionality for this.
+ # * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer)
+ # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
+ # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
# * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
@@ -850,24 +853,36 @@ module ActionView
I18n.translate(key, :locale => @options[:locale])
end
- # Lookup month name for number.
- # month_name(1) => "January"
+ # Looks up month names by number (1-based):
#
- # If <tt>:use_month_numbers</tt> option is passed
- # month_name(1) => 1
+ # month_name(1) # => "January"
#
- # If <tt>:use_two_month_numbers</tt> option is passed
- # month_name(1) => '01'
+ # If the <tt>:use_month_numbers</tt> option is passed:
#
- # If <tt>:add_month_numbers</tt> option is passed
- # month_name(1) => "1 - January"
+ # month_name(1) # => 1
+ #
+ # If the <tt>:use_two_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => '01'
+ #
+ # If the <tt>:add_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => "1 - January"
+ #
+ # If the <tt>:month_format_string</tt> option is passed:
+ #
+ # month_name(1) # => "January (01)"
+ #
+ # depending on the format string.
def month_name(number)
if @options[:use_month_numbers]
number
elsif @options[:use_two_digit_numbers]
- sprintf "%02d", number
+ '%02d' % number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
+ elsif format_string = @options[:month_format_string]
+ format_string % {number: number, name: month_names[number]}
else
month_names[number]
end
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index ad825cd1f1..7157a95146 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -384,20 +384,29 @@ module ActionView
def delegate_number_helper_method(method, number, options)
return unless number
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
+ options = escape_unsafe_options(options.symbolize_keys)
wrap_with_output_safety_handling(number, options.delete(:raise)) {
ActiveSupport::NumberHelper.public_send(method, number, options)
}
end
- def escape_unsafe_delimiters_and_separators(options)
- options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
- options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
- options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
+ def escape_unsafe_options(options)
+ options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
+ options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
+ options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
options
end
+ def escape_units(units)
+ Hash[units.map do |k, v|
+ [k, ERB::Util.html_escape(v)]
+ end]
+ end
+
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
valid_float = valid_float?(number)
raise InvalidNumberError, number if raise_on_invalid && !valid_float
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 458086de96..15b88bcda6 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -12,6 +12,13 @@ module ActionView
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
+ # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
+ # type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
+ # performs html escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
+ # * <tt>:body</tt> - Renders the text passed in, and does not set content
+ # type in the response.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index ffa67649da..9ee05bd816 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -420,7 +420,7 @@ module ActionView
end
def _include_layout?(options)
- (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
+ (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 668831dff3..be17097428 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -21,8 +21,14 @@ module ActionView
def determine_template(options) #:nodoc:
keys = options.fetch(:locals, {}).keys
- if options.key?(:text)
+ if options.key?(:body)
+ Template::Text.new(options[:body])
+ elsif options.key?(:text)
Template::Text.new(options[:text], formats.first)
+ elsif options.key?(:plain)
+ Template::Text.new(options[:plain])
+ elsif options.key?(:html)
+ Template::HTML.new(options[:html], formats.first)
elsif options.key?(:file)
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
elsif options.key?(:inline)
@@ -35,7 +41,7 @@ module ActionView
find_template(options[:template], options[:prefixes], false, keys, @details)
end
else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 99b95fdfb7..f96587c816 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -94,14 +94,19 @@ module ActionView
variant = options[:variant]
lookup_context.rendered_format = nil if options[:formats]
- lookup_context.variants = [variant] if variant
+ lookup_context.variants = variant if variant
view_renderer.render(view_context, options)
end
# Assign the rendered format to lookup context.
- def _process_format(format) #:nodoc:
+ def _process_format(format, options = {}) #:nodoc:
super
+
+ if options[:body]
+ self.no_content_type = true
+ end
+
lookup_context.formats = [format.to_sym]
lookup_context.rendered_format = lookup_context.formats.first
end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 9b0619f1aa..961a969b6e 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -90,6 +90,7 @@ module ActionView
eager_autoload do
autoload :Error
autoload :Handlers
+ autoload :HTML
autoload :Text
autoload :Types
end
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
new file mode 100644
index 0000000000..0321f819b5
--- /dev/null
+++ b/actionview/lib/action_view/template/html.rb
@@ -0,0 +1,34 @@
+module ActionView #:nodoc:
+ # = Action View HTML Template
+ class Template
+ class HTML #:nodoc:
+ attr_accessor :type
+
+ def initialize(string, type = nil)
+ @string = string.to_s
+ @type = Types[type] || type if type
+ @type ||= Types[:html]
+ end
+
+ def identifier
+ 'html template'
+ end
+
+ def inspect
+ 'html template'
+ end
+
+ def to_str
+ ERB::Util.h(@string)
+ end
+
+ def render(*args)
+ to_str
+ end
+
+ def formats
+ [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb
index 859c7bc3ce..04f5b8d17a 100644
--- a/actionview/lib/action_view/template/text.rb
+++ b/actionview/lib/action_view/template/text.rb
@@ -27,7 +27,7 @@ module ActionView #:nodoc:
end
def formats
- [@type.to_sym]
+ [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
end
end
end
diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb
index edb6d8f116..3d5d6c9be1 100644
--- a/actionview/lib/action_view/version.rb
+++ b/actionview/lib/action_view/version.rb
@@ -1,7 +1,7 @@
module ActionView
# Returns the version of the currently loaded ActionView as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta1"
+ Gem::Version.new "4.1.0.beta2"
end
module VERSION #:nodoc:
diff --git a/actionview/test/fixtures/customers/_customer.xml.erb b/actionview/test/fixtures/customers/_customer.xml.erb
new file mode 100644
index 0000000000..d3f1e0768f
--- /dev/null
+++ b/actionview/test/fixtures/customers/_customer.xml.erb
@@ -0,0 +1 @@
+<greeting><%= greeting %></greeting><name><%= customer.name %></name> \ No newline at end of file
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 5f09aef249..6f77c3c99d 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -326,6 +326,16 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_month(8, :add_month_numbers => true)
end
+ def test_select_month_with_format_string
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">January (01)</option>\n<option value="2">February (02)</option>\n<option value="3">March (03)</option>\n<option value="4">April (04)</option>\n<option value="5">May (05)</option>\n<option value="6">June (06)</option>\n<option value="7">July (07)</option>\n<option value="8" selected="selected">August (08)</option>\n<option value="9">September (09)</option>\n<option value="10">October (10)</option>\n<option value="11">November (11)</option>\n<option value="12">December (12)</option>\n)
+ expected << "</select>\n"
+
+ format_string = '%{name} (%<number>02d)'
+ assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :month_format_string => format_string)
+ assert_dom_equal expected, select_month(8, :month_format_string => format_string)
+ end
+
def test_select_month_with_numbers_and_names_with_abbv
expected = %(<select id="date_month" name="date[month]">\n)
expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n)
diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb
new file mode 100644
index 0000000000..549c12c88c
--- /dev/null
+++ b/actionview/test/template/html_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+class HTMLTest < ActiveSupport::TestCase
+ test 'formats returns symbol for recognized MIME type' do
+ assert_equal [:html], ActionView::Template::HTML.new('', :html).formats
+ end
+
+ test 'formats returns string for recognized MIME type when MIME does not have symbol' do
+ foo = Mime::Type.lookup("foo")
+ assert_nil foo.to_sym
+ assert_equal ['foo'], ActionView::Template::HTML.new('', foo).formats
+ end
+
+ test 'formats returns string for unknown MIME type' do
+ assert_equal ['foo'], ActionView::Template::HTML.new('', 'foo').formats
+ end
+end
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index be336ea3fb..11bc978324 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -8,6 +8,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "555-1234", number_to_phone(5551234)
assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123)
assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "")
+ assert_equal "+&lt;script&gt;&lt;/script&gt;8005551212", number_to_phone(8005551212, country_code: "<script></script>", delimiter: "")
+ assert_equal "8005551212 x &lt;script&gt;&lt;/script&gt;", number_to_phone(8005551212, extension: "<script></script>", delimiter: "")
end
def test_number_to_currency
@@ -16,11 +18,17 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0)
assert_equal "1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", unit: raw("K&#269;"), format: "%n %u", negative_format: "%n - %u")
assert_equal "&amp;pound;1,234,567,890.50", number_to_currency("1234567890.50", unit: "&pound;")
+ assert_equal "&lt;b&gt;1,234,567,890.50&lt;/b&gt; $", number_to_currency("1234567890.50", format: "<b>%n</b> %u")
+ assert_equal "&lt;b&gt;1,234,567,890.50&lt;/b&gt; $", number_to_currency("-1234567890.50", negative_format: "<b>%n</b> %u")
+ assert_equal "&lt;b&gt;1,234,567,890.50&lt;/b&gt; $", number_to_currency("-1234567890.50", 'negative_format' => "<b>%n</b> %u")
end
def test_number_to_percentage
assert_equal nil, number_to_percentage(nil)
assert_equal "100.000%", number_to_percentage(100)
+ assert_equal "100.000 %", number_to_percentage(100, format: '%n %')
+ assert_equal "&lt;b&gt;100.000&lt;/b&gt; %", number_to_percentage(100, format: '<b>%n</b> %')
+ assert_equal "<b>100.000</b> %", number_to_percentage(100, format: raw('<b>%n</b> %'))
assert_equal "100%", number_to_percentage(100, precision: 0)
assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true)
assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",")
@@ -52,6 +60,31 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false)
end
+ def test_number_to_human_escape_units
+ volume = { unit: "<b>ml</b>", thousand: "<b>lt</b>", million: "<b>m3</b>", trillion: "<b>km3</b>", quadrillion: "<b>Pl</b>" }
+ assert_equal '123 &lt;b&gt;lt&lt;/b&gt;', number_to_human(123456, :units => volume)
+ assert_equal '12 &lt;b&gt;ml&lt;/b&gt;', number_to_human(12, :units => volume)
+ assert_equal '1.23 &lt;b&gt;m3&lt;/b&gt;', number_to_human(1234567, :units => volume)
+ assert_equal '1.23 &lt;b&gt;km3&lt;/b&gt;', number_to_human(1_234_567_000_000, :units => volume)
+ assert_equal '1.23 &lt;b&gt;Pl&lt;/b&gt;', number_to_human(1_234_567_000_000_000, :units => volume)
+
+ #Including fractionals
+ distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>",
+ ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>",
+ micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>"}
+ assert_equal '1.23 &lt;b&gt;mm&lt;/b&gt;', number_to_human(0.00123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;cm&lt;/b&gt;', number_to_human(0.0123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;dm&lt;/b&gt;', number_to_human(0.123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;m&lt;/b&gt;', number_to_human(1.23, :units => distance)
+ assert_equal '1.23 &lt;b&gt;dam&lt;/b&gt;', number_to_human(12.3, :units => distance)
+ assert_equal '1.23 &lt;b&gt;hm&lt;/b&gt;', number_to_human(123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;km&lt;/b&gt;', number_to_human(1230, :units => distance)
+ assert_equal '1.23 &lt;b&gt;um&lt;/b&gt;', number_to_human(0.00000123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;nm&lt;/b&gt;', number_to_human(0.00000000123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;pm&lt;/b&gt;', number_to_human(0.00000000000123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;fm&lt;/b&gt;', number_to_human(0.00000000000000123, :units => distance)
+ end
+
def test_number_helpers_escape_delimiter_and_separator
assert_equal "111&lt;script&gt;&lt;/script&gt;111&lt;script&gt;&lt;/script&gt;1111", number_to_phone(1111111111, delimiter: "<script></script>")
@@ -73,6 +106,12 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "100&lt;script&gt;&lt;/script&gt;000 Quadrillion", number_to_human(10**20, delimiter: "<script></script>")
end
+ def test_number_to_human_with_custom_translation_scope
+ I18n.backend.store_translations 'ts',
+ :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal "1.01 cm", number_to_human(0.0101, :locale => 'ts', :units => :custom_units_for_number_to_human)
+ end
+
def test_number_helpers_outputs_are_html_safe
assert number_to_human(1).html_safe?
assert !number_to_human("<script></script>").html_safe?
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 055a273cc3..ca508abfb8 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -22,7 +22,7 @@ module RenderTestCases
def test_render_without_options
e = assert_raises(ArgumentError) { @view.render() }
- assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message
+ assert_match(/You invoked render but did not give any of (.+) option./, e.message)
end
def test_render_file
@@ -304,6 +304,16 @@ module RenderTestCases
assert_equal "Hola: david", @controller_view.render('customer_greeting', :greeting => 'Hola', :customer_greeting => Customer.new("david"))
end
+ def test_render_partial_with_object_uses_render_partial_path
+ assert_equal "Hello: lifo",
+ @controller_view.render(:partial => Customer.new("lifo"), :locals => {:greeting => "Hello"})
+ end
+
+ def test_render_partial_with_object_and_format_uses_render_partial_path
+ assert_equal "<greeting>Hello</greeting><name>lifo</name>",
+ @controller_view.render(:partial => Customer.new("lifo"), :formats => :xml, :locals => {:greeting => "Hello"})
+ end
+
def test_render_partial_using_object
assert_equal "Hello: lifo",
@controller_view.render(Customer.new("lifo"), :greeting => "Hello")
diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb
new file mode 100644
index 0000000000..d899d54589
--- /dev/null
+++ b/actionview/test/template/text_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+class TextTest < ActiveSupport::TestCase
+ test 'formats returns symbol for recognized MIME type' do
+ assert_equal [:text], ActionView::Template::Text.new('', :text).formats
+ end
+
+ test 'formats returns string for recognized MIME type when MIME does not have symbol' do
+ foo = Mime::Type.lookup("foo")
+ assert_nil foo.to_sym
+ assert_equal ['foo'], ActionView::Template::Text.new('', foo).formats
+ end
+
+ test 'formats returns string for unknown MIME type' do
+ assert_equal ['foo'], ActionView::Template::Text.new('', 'foo').formats
+ end
+end
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 58ba3ab9b2..f7c9534ffb 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
module ActiveModel
# Returns the version of the currently loaded ActiveModel as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta1"
+ Gem::Version.new "4.1.0.beta2"
end
module VERSION #:nodoc:
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 8b55901a65..2853476c91 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -41,6 +41,10 @@ class DirtyTest < ActiveModel::TestCase
def save
changes_applied
end
+
+ def reload
+ reset_changes
+ end
end
setup do
@@ -157,4 +161,19 @@ class DirtyTest < ActiveModel::TestCase
@model.size = 1
assert @model.size_changed?
end
+
+ test "reload should reset all changes" do
+ @model.name = 'Dmitry'
+ @model.name_changed?
+ @model.save
+ @model.name = 'Bob'
+
+ assert_equal [nil, 'Dmitry'], @model.previous_changes['name']
+ assert_equal 'Dmitry', @model.changed_attributes['name']
+
+ @model.reload
+
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1fd9003009..7efd75a239 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,89 @@
+* Default scopes are no longer overriden by chained conditions.
+
+ Before this change when you defined a `default_scope` in a model
+ it was overriden by chained conditions in the same field. Now it
+ is merged like any other scope.
+
+ Before:
+
+ class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+ end
+
+ User.all
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+ User.active
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+ User.where(state: 'inactive')
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+
+ After:
+
+ class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+ end
+
+ User.all
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+ User.active
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
+
+ User.where(state: 'inactive')
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
+
+ To get the previous behavior it is needed to explicitly remove the
+ `default_scope` condition using `unscoped`, `unscope`, `rewhere` or
+ `except`.
+
+ Example:
+
+ class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { unscope(where: :state).where(state: 'active') }
+ scope :inactive, -> { rewhere state: 'inactive' }
+ end
+
+ User.all
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+ User.active
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+ User.inactive
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+
+* Perform necessary deeper encoding when hstore is inside an array.
+
+ Fixes #11135.
+
+ *Josh Goodall*, *Genadi Samokovarov*
+
+* Properly detect if a connection is still active before using it
+ in multi-threaded environments.
+
+ Fixes #12867.
+
+ *Kevin Casey*, *Matthew Draper*, *William (B.J.) Snow Orvis*
+
+* When inverting add_index use the index name if present instead of
+ the columns.
+
+ If there are two indices with matching columns and one of them is
+ explicitly named then reverting the migration adding the named one
+ would instead drop the unnamed one.
+
+ The inversion of add_index will now drop the index by its name if
+ it is present.
+
+ *Hubert Dąbrowski*
+
* Add flag to disable schema dump after migration.
Add a config parameter on Active Record named `dump_schema_after_migration`
@@ -508,22 +594,6 @@
*Damien Mathieu*
-* Improve the default select when `from` is used.
-
- Previously, if you did something like Topic.from(:temp_topics), it
- would generate SQL like:
-
- SELECT topics.* FROM temp_topics;
-
- Which is will cause an error since there's not a topics table to select
- from.
-
- Now the default if you use from is just `*`:
-
- SELECT * FROM temp_topics;
-
- *Cody Cutrer*
-
* Fix `PostgreSQL` insert to properly extract table name from multiline string SQL.
Previously, executing an insert SQL in `PostgreSQL` with a command like this:
@@ -539,6 +609,10 @@
*Kuldeep Aggarwal*
+* Correctly escape PostgreSQL arrays.
+
+ Fixes: CVE-2014-0080
+
* `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert
to an `Array` by calling `#to_a` before using these methods.
@@ -738,7 +812,7 @@
* Raise `ActiveRecord::RecordNotDestroyed` when a replaced child
marked with `dependent: destroy` fails to be destroyed.
- Fixex #12812.
+ Fixes #12812.
*Brian Thomas Storti*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index b5e21cbede..142d21ce92 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -130,7 +130,6 @@ module ActiveRecord
autoload :JoinDependency, 'active_record/associations/join_dependency'
autoload :AssociationScope, 'active_record/associations/association_scope'
autoload :AliasTracker, 'active_record/associations/alias_tracker'
- autoload :JoinHelper, 'active_record/associations/join_helper'
end
# Clears out the association cache.
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 0c23029981..85109aee6c 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -5,16 +5,48 @@ module ActiveRecord
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
# ActiveRecord::Associations::ThroughAssociationScope
class AliasTracker # :nodoc:
- attr_reader :aliases, :table_joins, :connection
+ attr_reader :aliases, :connection
+
+ def self.empty(connection)
+ new connection, Hash.new(0)
+ end
+
+ def self.create(connection, table_joins)
+ if table_joins.empty?
+ empty connection
+ else
+ aliases = Hash.new { |h,k|
+ h[k] = initial_count_for(connection, k, table_joins)
+ }
+ new connection, aliases
+ end
+ end
+
+ def self.initial_count_for(connection, name, table_joins)
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = connection.quote_table_name(name).downcase
+
+ counts = table_joins.map do |join|
+ if join.is_a?(Arel::Nodes::StringJoin)
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ else
+ join.left.table_name == name ? 1 : 0
+ end
+ end
+
+ counts.sum
+ end
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection = Base.connection, table_joins = [])
- @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
- @table_joins = table_joins
- @connection = connection
+ def initialize(connection, aliases)
+ @aliases = aliases
+ @connection = connection
end
- def aliased_table_for(table_name, aliased_name = nil)
+ def aliased_table_for(table_name, aliased_name)
table_alias = aliased_name_for(table_name, aliased_name)
if table_alias == table_name
@@ -24,9 +56,7 @@ module ActiveRecord
end
end
- def aliased_name_for(table_name, aliased_name = nil)
- aliased_name ||= table_name
-
+ def aliased_name_for(table_name, aliased_name)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
@@ -48,26 +78,6 @@ module ActiveRecord
private
- def initial_count_for(name)
- return 0 if Arel::Table === table_joins
-
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = connection.quote_table_name(name).downcase
-
- counts = table_joins.map do |join|
- if join.is_a?(Arel::Nodes::StringJoin)
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- else
- join.left.table_name == name ? 1 : 0
- end
- end
-
- counts.sum
- end
-
def truncate(name)
name.slice(0, connection.table_alias_length - 2)
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 67ea489b22..9ad2d2fb12 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -94,7 +94,7 @@ module ActiveRecord
# actually gets built.
def association_scope
if klass
- @association_scope ||= AssociationScope.new(self).scope
+ @association_scope ||= AssociationScope.scope(self, klass.connection)
end
end
@@ -232,7 +232,7 @@ module ActiveRecord
# Returns true if record contains the foreign_key
def foreign_key_for?(record)
- record.attributes.has_key? reflection.foreign_key
+ record.has_attribute?(reflection.foreign_key)
end
# This should be implemented to return the values of the relevant key(s) on the owner,
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 5a0ba9e6b1..27fd9e35db 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -1,54 +1,77 @@
-require 'active_record/associations/join_helper'
-
module ActiveRecord
module Associations
class AssociationScope #:nodoc:
- include JoinHelper
+ INSTANCE = new
- attr_reader :association, :alias_tracker
+ def self.scope(association, connection)
+ INSTANCE.scope association, connection
+ end
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
- delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
+ def scope(association, connection)
+ klass = association.klass
+ reflection = association.reflection
+ scope = klass.unscoped
+ owner = association.owner
+ alias_tracker = AliasTracker.empty connection
- def initialize(association)
- @association = association
- @alias_tracker = AliasTracker.new klass.connection
+ scope.extending! Array(reflection.options[:extend])
+ add_constraints(scope, owner, klass, reflection, alias_tracker)
end
- def scope
- scope = klass.unscoped
- scope.extending! Array(options[:extend])
- add_constraints(scope)
+ def join_type
+ Arel::Nodes::InnerJoin
end
private
- def column_for(table_name, column_name)
+ def construct_tables(chain, klass, refl, alias_tracker)
+ chain.map do |reflection|
+ alias_tracker.aliased_table_for(
+ table_name_for(reflection, klass, refl),
+ table_alias_for(reflection, refl, reflection != refl)
+ )
+ end
+ end
+
+ def table_alias_for(reflection, refl, join = false)
+ name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
+ name << "_join" if join
+ name
+ end
+
+ def join(table, constraint)
+ table.create_join(table, table.create_on(constraint), join_type)
+ end
+
+ def column_for(table_name, column_name, alias_tracker)
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
columns[column_name]
end
- def bind_value(scope, column, value)
+ def bind_value(scope, column, value, alias_tracker)
substitute = alias_tracker.connection.substitute_at(
column, scope.bind_values.length)
scope.bind_values += [[column, value]]
substitute
end
- def bind(scope, table_name, column_name, value)
- column = column_for table_name, column_name
- bind_value scope, column, value
+ def bind(scope, table_name, column_name, value, tracker)
+ column = column_for table_name, column_name, tracker
+ bind_value scope, column, value, tracker
end
- def add_constraints(scope)
- tables = construct_tables
+ def add_constraints(scope, owner, assoc_klass, refl, tracker)
+ chain = refl.chain
+ scope_chain = refl.scope_chain
+
+ tables = construct_tables(chain, assoc_klass, refl, tracker)
chain.each_with_index do |reflection, i|
table, foreign_table = tables.shift, tables.first
if reflection.source_macro == :belongs_to
if reflection.options[:polymorphic]
- key = reflection.association_primary_key(self.klass)
+ key = reflection.association_primary_key(assoc_klass)
else
key = reflection.association_primary_key
end
@@ -60,12 +83,12 @@ module ActiveRecord
end
if reflection == chain.last
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
scope = scope.where(table[key].eq(bind_val))
if reflection.type
value = owner.class.base_class.name
- bind_val = bind scope, table.table_name, reflection.type.to_s, value
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
scope = scope.where(table[reflection.type].eq(bind_val))
end
else
@@ -73,7 +96,7 @@ module ActiveRecord
if reflection.type
value = chain[i + 1].klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type.to_s, value
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
scope = scope.where(table[reflection.type].eq(bind_val))
end
@@ -81,14 +104,14 @@ module ActiveRecord
end
is_first_chain = i == 0
- klass = is_first_chain ? self.klass : reflection.klass
+ klass = is_first_chain ? assoc_klass : reflection.klass
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
scope_chain[i].each do |scope_chain_item|
- item = eval_scope(klass, scope_chain_item)
+ item = eval_scope(klass, scope_chain_item, owner)
- if scope_chain_item == self.reflection.scope
+ if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes, :bind)
end
@@ -104,22 +127,22 @@ module ActiveRecord
scope
end
- def alias_suffix
- reflection.name
+ def alias_suffix(refl)
+ refl.name
end
- def table_name_for(reflection)
- if reflection == self.reflection
+ def table_name_for(reflection, klass, refl)
+ if reflection == refl
# If this is a polymorphic belongs_to, we want to get the klass from the
# association because it depends on the polymorphic_type attribute of
# the owner
klass.table_name
else
- super
+ reflection.table_name
end
end
- def eval_scope(klass, scope)
+ def eval_scope(klass, scope, owner)
if scope.is_a?(Relation)
scope
else
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 295dccf34e..94f69d4c2d 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -93,8 +93,8 @@ module ActiveRecord
# joins # => []
#
def initialize(base, associations, joins)
- @alias_tracker = AliasTracker.new(base.connection, joins)
- @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
+ @alias_tracker = AliasTracker.create(base.connection, joins)
+ @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -163,7 +163,7 @@ module ActiveRecord
def make_outer_joins(parent, child)
tables = table_aliases_for(parent, child)
- join_type = Arel::OuterJoin
+ join_type = Arel::Nodes::OuterJoin
joins = make_constraints parent, child, tables, join_type
joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
@@ -171,7 +171,7 @@ module ActiveRecord
def make_inner_joins(parent, child)
tables = child.tables
- join_type = Arel::InnerJoin
+ join_type = Arel::Nodes::InnerJoin
joins = make_constraints parent, child, tables, join_type
joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0cd2e1a816..cee3c9999f 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -25,7 +25,8 @@ module ActiveRecord
joins = []
tables = tables.reverse
- scope_chain_iter = scope_chain.reverse_each
+ scope_chain_index = 0
+ scope_chain = scope_chain.reverse
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -44,13 +45,14 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
- scope_chain_items = scope_chain_iter.next.map do |item|
+ scope_chain_items = scope_chain[scope_chain_index].map do |item|
if item.is_a?(Relation)
item
else
ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
end
end
+ scope_chain_index += 1
scope_chain_items.concat [klass.send(:build_default_scope)].compact
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
deleted file mode 100644
index f345d16841..0000000000
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveRecord
- module Associations
- # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
- module JoinHelper #:nodoc:
-
- def join_type
- Arel::InnerJoin
- end
-
- private
-
- def construct_tables
- chain.map do |reflection|
- alias_tracker.aliased_table_for(
- table_name_for(reflection),
- table_alias_for(reflection, reflection != self.reflection)
- )
- end
- end
-
- def table_name_for(reflection)
- reflection.table_name
- end
-
- def table_alias_for(reflection, join = false)
- name = "#{reflection.plural_name}_#{alias_suffix}"
- name << "_join" if join
- name
- end
-
- def join(table, constraint)
- table.create_join(table, table.create_on(constraint), join_type)
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index cebe741daa..fd185de589 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -393,7 +393,7 @@ module ActiveRecord
synchronize do
stale = Time.now - @dead_connection_timeout
connections.dup.each do |conn|
- if conn.in_use? && stale > conn.last_use && !conn.active?
+ if conn.in_use? && stale > conn.last_use && !conn.active_threadsafe?
remove conn
end
end
@@ -538,7 +538,10 @@ module ActiveRecord
# for (not necessarily the current class).
def retrieve_connection(klass) #:nodoc:
pool = retrieve_connection_pool(klass)
- (pool && pool.connection) or raise ConnectionNotEstablished
+ raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ conn = pool.connection
+ raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ conn
end
# Returns true if a connection that's accessible to this class has
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 3c94bad208..11b28a4858 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -262,6 +262,12 @@ module ActiveRecord
def active?
end
+ # Adapter should redefine this if it needs a threadsafe way to approximate
+ # if the connection is active
+ def active_threadsafe?
+ active?
+ end
+
# Disconnects from the database if already connected, and establishes a
# new connection with the database. Implementors should call super if they
# override the default implementation.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 7768c24967..23edc8b955 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -298,11 +298,7 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
- if name == :skip_logging
- @connection.query(sql)
- else
- log(sql, name) { @connection.query(sql) }
- end
+ log(sql, name) { @connection.query(sql) }
end
# MysqlAdapter has to free a result after using it, so we use this method to write
@@ -775,7 +771,7 @@ module ActiveRecord
end.compact.join(', ')
# ...and send them all in one query
- execute("SET #{encoding} #{variable_assignments}", :skip_logging)
+ @connection.query "SET #{encoding} #{variable_assignments}"
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 35ce881302..551a9289c3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -35,11 +35,11 @@ module ActiveRecord
end
end
- def hstore_to_string(object)
+ def hstore_to_string(object, array_member = false)
if Hash === object
- object.map { |k,v|
- "#{escape_hstore(k)}=>#{escape_hstore(v)}"
- }.join ','
+ string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',')
+ string = escape_hstore(string) if array_member
+ string
else
object
end
@@ -49,10 +49,10 @@ module ActiveRecord
if string.nil?
nil
elsif String === string
- Hash[string.scan(HstorePair).map { |k,v|
+ Hash[string.scan(HstorePair).map { |k, v|
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
- [k,v]
+ [k, v]
}]
else
string
@@ -142,12 +142,16 @@ module ActiveRecord
end
end
+ ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays
+
def quote_and_escape(value)
case value
when "NULL", Numeric
value
else
- "\"#{value.gsub(/"/,"\\\"")}\""
+ value = value.gsub(/\\/, ARRAY_ESCAPE)
+ value.gsub!(/"/,"\\\"")
+ "\"#{value}\""
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index c1f978a081..210172cf32 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -121,7 +121,7 @@ module ActiveRecord
end
when Hash
case column.sql_type
- when 'hstore' then PostgreSQLColumn.hstore_to_string(value)
+ when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member)
when 'json' then PostgreSQLColumn.json_to_string(value)
else super(value, column)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 9618ba4087..36c7462419 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -586,11 +586,16 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.connect_poll != PG::PGRES_POLLING_FAILED
+ @connection.query 'SELECT 1'
+ true
rescue PGError
false
end
+ def active_threadsafe?
+ @connection.connect_poll != PG::PGRES_POLLING_FAILED
+ end
+
# Close then reopen the connection.
def reconnect!
super
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index b57da73969..b6b02322d7 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -385,8 +385,8 @@ module ActiveRecord
attr_accessor :delegate # :nodoc:
attr_accessor :disable_ddl_transaction # :nodoc:
- def check_pending!
- raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
+ def check_pending!(connection = Base.connection)
+ raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
end
def load_schema_if_pending!
@@ -830,17 +830,17 @@ module ActiveRecord
SchemaMigration.all.map { |x| x.version.to_i }.sort
end
- def current_version
+ def current_version(connection = Base.connection)
sm_table = schema_migrations_table_name
- if Base.connection.table_exists?(sm_table)
+ if connection.table_exists?(sm_table)
get_all_versions.max || 0
else
0
end
end
- def needs_migration?
- current_version < last_version
+ def needs_migration?(connection = Base.connection)
+ current_version(connection) < last_version
end
def last_version
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 9139ad953c..c44d8c1665 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -140,7 +140,12 @@ module ActiveRecord
def invert_add_index(args)
table, columns, options = *args
- [:remove_index, [table, (options || {}).merge(column: columns)]]
+ options ||= {}
+
+ index_name = options[:name]
+ options_hash = index_name ? { name: index_name } : { column: columns }
+
+ [:remove_index, [table, options_hash]]
end
def invert_remove_index(args)
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 9be734e88e..1d5c80bc01 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -75,7 +75,7 @@ db_namespace = namespace :db do
# desc 'Runs the "down" for a given migration VERSION.'
task :down => [:environment, :load_config] do
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
- raise 'VERSION is required' unless version
+ raise 'VERSION is required - To go down one migration, run db:rollback' unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
db_namespace['_dump'].invoke
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 01d46f7676..7099bdd285 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -347,7 +347,15 @@ module ActiveRecord
end
def construct_relation_for_association_calculations
- apply_join_dependency(self, construct_join_dependency(arel.froms.first))
+ from = arel.froms.first
+ if Arel::Table === from
+ apply_join_dependency(self, construct_join_dependency)
+ else
+ # FIXME: as far as I can tell, `from` will always be an Arel::Table.
+ # There are no tests that test this branch, but presumably it's
+ # possible for `from` to be a list?
+ apply_join_dependency(self, construct_join_dependency(from))
+ end
end
def apply_join_dependency(relation, join_dependency)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 077f09b67d..5d38f0dce8 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -240,7 +240,7 @@ module ActiveRecord
def select!(*fields) # :nodoc:
fields.flatten!
fields.map! do |field|
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
+ klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
end
self.select_values += fields
self
@@ -995,8 +995,6 @@ module ActiveRecord
columns_hash.key?(field.to_s) ? arel_table[field] : field
end
arel.project(*expanded_select)
- elsif from_value
- arel.project(Arel.star)
else
arel.project(@klass.arel_table[Arel.star])
end
@@ -1052,11 +1050,11 @@ module ActiveRecord
order_args.map! do |arg|
case arg
when Symbol
- arg = klass.attribute_alias(arg).to_sym if klass.attribute_alias?(arg)
+ arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
table[arg].asc
when Hash
arg.map { |field, dir|
- field = klass.attribute_alias(field).to_sym if klass.attribute_alias?(field)
+ field = klass.attribute_alias(field) if klass.attribute_alias?(field)
table[field].send(dir)
}
else
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index dacaec26b7..5a71c13d91 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -29,6 +29,7 @@ module ActiveRecord
end
end
alias_method :sanitize_sql, :sanitize_sql_for_conditions
+ alias_method :sanitize_conditions, :sanitize_sql
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
@@ -122,8 +123,6 @@ module ActiveRecord
end
end
- alias_method :sanitize_conditions, :sanitize_sql
-
def replace_bind_variables(statement, values) #:nodoc:
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
bound = values.dup
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 863c3ebe4d..7795561e51 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,7 +1,7 @@
module ActiveRecord
# Returns the version of the currently loaded ActiveRecord as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta1"
+ Gem::Version.new "4.1.0.beta2"
end
module VERSION #:nodoc:
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
new file mode 100644
index 0000000000..7202cce390
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlCompositeTest < ActiveRecord::TestCase
+ class PostgresqlComposite < ActiveRecord::Base
+ self.table_name = "postgresql_composites"
+ end
+
+ def teardown
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
+ @connection.execute 'DROP TYPE IF EXISTS full_address'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.execute <<-SQL
+ CREATE TYPE full_address AS
+ (
+ city VARCHAR(90),
+ street VARCHAR(90)
+ );
+ SQL
+ @connection.create_table('postgresql_composites') do |t|
+ t.column :address, :full_address
+ end
+ end
+ end
+
+ def test_composite_mapping
+ @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
+ composite = PostgresqlComposite.first
+ assert_equal "(Paris,Champs-Élysées)", composite.address
+
+ composite.address = "(Paris,Rue Basse)"
+ composite.save!
+
+ assert_equal '(Paris,"Rue Basse")', composite.reload.address
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 90cca7d3e6..4715fa002d 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -91,40 +91,50 @@ module ActiveRecord
assert_operator plan.length, :>, 0
end
- # Must have with_manual_interventions set to true for this
- # test to run.
+ # Must have PostgreSQL >= 9.2, or with_manual_interventions set to
+ # true for this test to run.
+ #
# When prompted, restart the PostgreSQL server with the
# "-m fast" option or kill the individual connection assuming
# you know the incantation to do that.
# To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ...
# sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast"
- if ARTest.config['with_manual_interventions']
- def test_reconnection_after_actual_disconnection_with_verify
- original_connection_pid = @connection.query('select pg_backend_pid()')
+ def test_reconnection_after_actual_disconnection_with_verify
+ original_connection_pid = @connection.query('select pg_backend_pid()')
- # Sanity check.
- assert @connection.active?
+ # Sanity check.
+ assert @connection.active?
+ if @connection.send(:postgresql_version) >= 90200
+ secondary_connection = ActiveRecord::Base.connection_pool.checkout
+ secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")
+ ActiveRecord::Base.connection_pool.checkin(secondary_connection)
+ elsif ARTest.config['with_manual_interventions']
puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' +
'server with the "-m fast" option) and then press enter.'
$stdin.gets
+ else
+ # We're not capable of terminating the backend ourselves, and
+ # we're not allowed to seek assistance; bail out without
+ # actually testing anything.
+ return
+ end
- @connection.verify!
+ @connection.verify!
- assert @connection.active?
+ assert @connection.active?
- # If we get no exception here, then either we re-connected successfully, or
- # we never actually got disconnected.
- new_connection_pid = @connection.query('select pg_backend_pid()')
+ # If we get no exception here, then either we re-connected successfully, or
+ # we never actually got disconnected.
+ new_connection_pid = @connection.query('select pg_backend_pid()')
- assert_not_equal original_connection_pid, new_connection_pid,
- "umm -- looks like you didn't break the connection, because we're still " +
- "successfully querying with the same connection pid."
+ assert_not_equal original_connection_pid, new_connection_pid,
+ "umm -- looks like you didn't break the connection, because we're still " +
+ "successfully querying with the same connection pid."
- # Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ # Repair all fixture connections so other tests won't break.
+ @fixture_connections.each do |c|
+ c.verify!
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 04a458fbce..5c3a797c41 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -78,6 +78,14 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all)
end
+ def test_array_escaping
+ unknown = %(foo\\",bar,baz,\\)
+ nicknames = ["hello_#{unknown}"]
+ ar = PostgresqlArray.create!(nicknames: nicknames, id: 100)
+ ar.reload
+ assert_equal nicknames, ar.nicknames
+ end
+
def test_data_type_of_array_types
assert_equal :integer, @first_array.column_for_attribute(:commission_by_quarter).type
assert_equal :text, @first_array.column_for_attribute(:nicknames).type
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index d8782f5eaa..f2502430de 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -24,6 +24,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
@connection.transaction do
@connection.create_table('hstores') do |t|
t.hstore 'tags', :default => ''
+ t.hstore 'payload', array: true
t.hstore 'settings'
end
end
@@ -182,6 +183,30 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_equal({'1' => '2'}, x.tags)
end
+ def test_array_cycle
+ assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}])
+ end
+
+ def test_array_strings_with_quotes
+ assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}])
+ end
+
+ def test_array_strings_with_commas
+ assert_array_cycle([{'this,has' => 'many,values'}])
+ end
+
+ def test_array_strings_with_array_delimiters
+ assert_array_cycle(['{' => '}'])
+ end
+
+ def test_array_strings_with_null_strings
+ assert_array_cycle([{'NULL' => 'NULL'}])
+ end
+
+ def test_contains_nils
+ assert_array_cycle([{'NULL' => nil}])
+ end
+
def test_select_multikey
@connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
x = Hstore.first
@@ -237,6 +262,20 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
private
+ def assert_array_cycle(array)
+ # test creation
+ x = Hstore.create!(payload: array)
+ x.reload
+ assert_equal(array, x.payload)
+
+ # test updating
+ x = Hstore.create!(payload: [])
+ x.payload = array
+ x.save!
+ x.reload
+ assert_equal(array, x.payload)
+ end
+
def assert_cycle(hash)
# test creation
x = Hstore.create!(:tags => hash)
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 131080913c..019406dd84 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -6,21 +6,21 @@ module ActiveRecord
class PostgreSQLAdapterTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
end
def test_bad_connection
assert_raise ActiveRecord::NoDatabaseError do
configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db')
connection = ActiveRecord::Base.postgresql_connection(configuration)
- connection.exec_query('drop table if exists ex')
+ connection.exec_query('SELECT 1')
end
end
def test_valid_column
- column = @connection.columns('ex').find { |col| col.name == 'id' }
- assert @connection.valid_type?(column.type)
+ with_example_table do
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ assert @connection.valid_type?(column.type)
+ end
end
def test_invalid_column
@@ -28,7 +28,9 @@ module ActiveRecord
end
def test_primary_key
- assert_equal 'id', @connection.primary_key('ex')
+ with_example_table do
+ assert_equal 'id', @connection.primary_key('ex')
+ end
end
def test_primary_key_works_tables_containing_capital_letters
@@ -36,15 +38,15 @@ module ActiveRecord
end
def test_non_standard_primary_key
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(data character varying(255) primary key)')
- assert_equal 'data', @connection.primary_key('ex')
+ with_example_table 'data character varying(255) primary key' do
+ assert_equal 'data', @connection.primary_key('ex')
+ end
end
def test_primary_key_returns_nil_for_no_pk
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id integer)')
- assert_nil @connection.primary_key('ex')
+ with_example_table 'id integer' do
+ assert_nil @connection.primary_key('ex')
+ end
end
def test_primary_key_raises_error_if_table_not_found
@@ -54,32 +56,40 @@ module ActiveRecord
end
def test_insert_sql_with_proprietary_returning_clause
- id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
- assert_equal "5150", id
+ with_example_table do
+ id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
+ assert_equal "5150", id
+ end
end
def test_insert_sql_with_quoted_schema_and_table_name
- id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
+ with_example_table do
+ id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
end
def test_insert_sql_with_no_space_after_table_name
- id = @connection.insert_sql("insert into ex(number) values(5150)")
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
+ with_example_table do
+ id = @connection.insert_sql("insert into ex(number) values(5150)")
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
end
def test_multiline_insert_sql
- id = @connection.insert_sql(<<-SQL)
- insert into ex(
- number)
- values(
- 5152
- )
- SQL
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
+ with_example_table do
+ id = @connection.insert_sql(<<-SQL)
+ insert into ex(
+ number)
+ values(
+ 5152
+ )
+ SQL
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
end
def test_insert_sql_with_returning_disabled
@@ -135,29 +145,31 @@ module ActiveRecord
end
def test_pk_and_sequence_for
- pk, seq = @connection.pk_and_sequence_for('ex')
- assert_equal 'id', pk
- assert_equal @connection.default_sequence_name('ex', 'id'), seq
+ with_example_table do
+ pk, seq = @connection.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @connection.default_sequence_name('ex', 'id'), seq
+ end
end
def test_pk_and_sequence_for_with_non_standard_primary_key
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(code serial primary key)')
- pk, seq = @connection.pk_and_sequence_for('ex')
- assert_equal 'code', pk
- assert_equal @connection.default_sequence_name('ex', 'code'), seq
+ with_example_table 'code serial primary key' do
+ pk, seq = @connection.pk_and_sequence_for('ex')
+ assert_equal 'code', pk
+ assert_equal @connection.default_sequence_name('ex', 'code'), seq
+ end
end
def test_pk_and_sequence_for_returns_nil_if_no_seq
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id integer primary key)')
- assert_nil @connection.pk_and_sequence_for('ex')
+ with_example_table 'id integer primary key' do
+ assert_nil @connection.pk_and_sequence_for('ex')
+ end
end
def test_pk_and_sequence_for_returns_nil_if_no_pk
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id integer)')
- assert_nil @connection.pk_and_sequence_for('ex')
+ with_example_table 'id integer' do
+ assert_nil @connection.pk_and_sequence_for('ex')
+ end
end
def test_pk_and_sequence_for_returns_nil_if_table_not_found
@@ -165,23 +177,27 @@ module ActiveRecord
end
def test_exec_insert_number
- insert(@connection, 'number' => 10)
+ with_example_table do
+ insert(@connection, 'number' => 10)
- result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
+ result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
- assert_equal 1, result.rows.length
- assert_equal "10", result.rows.last.last
+ assert_equal 1, result.rows.length
+ assert_equal "10", result.rows.last.last
+ end
end
def test_exec_insert_string
- str = 'いただきます!'
- insert(@connection, 'number' => 10, 'data' => str)
+ with_example_table do
+ str = 'いただきます!'
+ insert(@connection, 'number' => 10, 'data' => str)
- result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
+ result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
- value = result.rows.last.last
+ value = result.rows.last.last
- assert_equal str, value
+ assert_equal str, value
+ end
end
def test_table_alias_length
@@ -191,44 +207,50 @@ module ActiveRecord
end
def test_exec_no_binds
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
-
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [['1', 'foo']], result.rows
+ with_example_table do
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 0, result.rows.length
+ assert_equal 2, result.columns.length
+ assert_equal %w{ id data }, result.columns
+
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_exec_with_binds
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_exec_typecasts_bind_vals
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- column = @connection.columns('ex').find { |col| col.name == 'id' }
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_substitute_at
@@ -240,9 +262,11 @@ module ActiveRecord
end
def test_partial_index
- @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
- index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
- assert_equal "(number > 100)", index.where
+ with_example_table do
+ @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
+ index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
+ assert_equal "(number > 100)", index.where
+ end
end
def test_columns_for_distinct_zero_orders
@@ -300,6 +324,14 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
+ def with_example_table(definition = nil)
+ definition ||= 'id serial primary key, number integer, data character varying(255)'
+ @connection.exec_query("create table ex(#{definition})")
+ yield
+ ensure
+ @connection.exec_query('drop table if exists ex')
+ end
+
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index 89210866f0..4d29a20e66 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -77,7 +77,7 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_bc_timestamp
- date = Date.new(0) - 1.second
+ date = Date.new(0) - 1.week
Developer.create!(:name => "aaron", :updated_at => date)
assert_equal date, Developer.find_by_name("aaron").updated_at
end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index e78cb88562..b478db749d 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class CopyTableTest < ActiveRecord::TestCase
- fixtures :customers, :companies, :comments, :binaries
+ fixtures :customers
def setup
@connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 02834edf7b..73cb739b2b 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -33,9 +33,8 @@ module ActiveRecord
end
end
- def test_connect_with_url
- skip "can't establish new connection when using memory db" if in_memory_db?
- begin
+ unless in_memory_db?
+ def test_connect_with_url
original_connection = ActiveRecord::Base.remove_connection
tf = Tempfile.open 'whatever'
url = "sqlite3://#{tf.path}"
@@ -46,11 +45,8 @@ module ActiveRecord
tf.unlink
ActiveRecord::Base.establish_connection(original_connection)
end
- end
- def test_connect_memory_with_url
- skip "can't establish new connection when using memory db" if in_memory_db?
- begin
+ def test_connect_memory_with_url
original_connection = ActiveRecord::Base.remove_connection
url = "sqlite3:///:memory:"
ActiveRecord::Base.establish_connection(url)
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index d38648202e..c78b036f53 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -6,8 +6,9 @@ module ActiveRecord
module Associations
class AssociationScopeTest < ActiveRecord::TestCase
test 'does not duplicate conditions' do
- association_scope = AssociationScope.new(Author.new.association(:welcome_posts))
- wheres = association_scope.scope.where_values.map(&:right)
+ scope = AssociationScope.scope(Author.new.association(:welcome_posts),
+ Author.connection)
+ wheres = scope.where_values.map(&:right)
assert_equal wheres.uniq, wheres
end
end
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 669569a774..b12bc355e8 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
+if ActiveRecord::Base.connection.supports_migrations?
class EagerSingularizationTest < ActiveRecord::TestCase
class Virus < ActiveRecord::Base
belongs_to :octopus
@@ -50,8 +51,6 @@ class EagerSingularizationTest < ActiveRecord::TestCase
end
def setup
- skip 'Does not support migrations' unless connection.supports_migrations?
-
connection.create_table :viri do |t|
t.column :octopus_id, :integer
t.column :species, :string
@@ -146,3 +145,4 @@ class EagerSingularizationTest < ActiveRecord::TestCase
end
end
end
+end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 47592f312e..026a7fe635 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -817,6 +817,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert author.named_categories(true).include?(category)
end
+ def test_collection_exists
+ author = authors(:mary)
+ category = Category.create!(author_ids: [author.id], name: "Primary")
+ assert category.authors.exists?(id: author.id)
+ assert category.reload.authors.exists?(id: author.id)
+ end
+
def test_collection_delete_with_nonstandard_primary_key_on_belongs_to
author = authors(:mary)
category = author.named_categories.create(:name => "Primary")
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 7a0c335627..d2f97df0fc 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -17,8 +17,35 @@ require 'models/tag'
require 'models/tagging'
require 'models/treasure'
require 'models/eye'
+require 'models/electron'
+require 'models/molecule'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
+ def test_autosave_validation
+ person = Class.new(ActiveRecord::Base) {
+ self.table_name = 'people'
+ validate :should_be_cool, :on => :create
+ def self.name; 'Person'; end
+
+ private
+
+ def should_be_cool
+ unless self.first_name == 'cool'
+ errors.add :first_name, "not cool"
+ end
+ end
+ }
+ reference = Class.new(ActiveRecord::Base) {
+ self.table_name = "references"
+ def self.name; 'Reference'; end
+ belongs_to :person, autosave: true, class: person
+ }
+
+ u = person.create!(first_name: 'cool')
+ u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create'
+ assert reference.create!(person: u).persisted?
+ end
+
def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
end
@@ -343,6 +370,33 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
end
+class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
+ def test_invalid_adding_with_nested_attributes
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+ molecule.save
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid'
+ end
+
+ def test_valid_adding_with_nested_attributes
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+
+ molecule.electrons = [valid_electron]
+ molecule.save
+
+ assert valid_electron.valid?
+ assert molecule.persisted?
+ assert_equal 1, molecule.electrons.count
+ end
+end
+
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
fixtures :companies, :people
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 2da51ea015..1cf215017b 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -142,7 +142,7 @@ module ActiveRecord
connections = @pool.connections.dup
connections.each do |conn|
- conn.extend(Module.new { def active?; false; end; })
+ conn.extend(Module.new { def active_threadsafe?; false; end; })
end
@pool.reap
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f3a4887a85..37c6af74da 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -628,7 +628,9 @@ class LoadAllFixturesTest < ActiveRecord::TestCase
self.class.fixture_path = FIXTURES_ROOT + "/all"
self.class.fixtures :all
- assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ if File.symlink? FIXTURES_ROOT + "/all/admin"
+ assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ end
ensure
ActiveRecord::FixtureSet.reset_cache
end
@@ -639,7 +641,9 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join('all')
self.class.fixtures :all
- assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ if File.symlink? FIXTURES_ROOT + "/all/admin"
+ assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ end
ensure
ActiveRecord::FixtureSet.reset_cache
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 428145d00b..debacf815c 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -106,6 +106,22 @@ module ActiveRecord
end
end
+ class RevertNamedIndexMigration1 < SilentMigration
+ def change
+ create_table("horses") do |t|
+ t.column :content, :string
+ t.column :remind_at, :datetime
+ end
+ add_index :horses, :content
+ end
+ end
+
+ class RevertNamedIndexMigration2 < SilentMigration
+ def change
+ add_index :horses, :content, name: "horses_index_named"
+ end
+ end
+
def teardown
%w[horses new_horses].each do |table|
if ActiveRecord::Base.connection.table_exists?(table)
@@ -255,5 +271,17 @@ module ActiveRecord
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
+ def test_migrate_revert_add_index_with_name
+ RevertNamedIndexMigration1.new.migrate(:up)
+ RevertNamedIndexMigration2.new.migrate(:up)
+ RevertNamedIndexMigration2.new.migrate(:down)
+
+ connection = ActiveRecord::Base.connection
+ assert connection.index_exists?(:horses, :content),
+ "index on content should exist"
+ assert !connection.index_exists?(:horses, :content, name: "horses_index_named"),
+ "horses_index_named index should not exist"
+ end
+
end
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 35b656ee43..a925cf4c05 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -174,13 +174,13 @@ module ActiveRecord
end
def test_invert_add_index
- remove = @recorder.inverse_of :add_index, [:table, [:one, :two], options: true]
- assert_equal [:remove_index, [:table, {column: [:one, :two], options: true}]], remove
+ remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
+ assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove
end
def test_invert_add_index_with_name
remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"]
- assert_equal [:remove_index, [:table, {column: [:one, :two], name: "new_index"}]], remove
+ assert_equal [:remove_index, [:table, {name: "new_index"}]], remove
end
def test_invert_add_index_with_no_options
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index e53a27d5dd..b62a41c08e 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -69,7 +69,7 @@ module ActiveRecord
conn = pool.checkout
count = pool.connections.length
- conn.extend(Module.new { def active?; false; end; })
+ conn.extend(Module.new { def active_threadsafe?; false; end; })
while count == pool.connections.length
Thread.pass
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index e390d37871..8718110c36 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -151,11 +151,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a
end
- def test_finding_with_subquery_without_select
- relation = Topic.where(:approved => true)
- assert_equal relation.to_a, Topic.from(relation).to_a
+ def test_finding_with_subquery_without_select_does_not_change_the_select
+ relation = Topic.where(approved: true)
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Topic.from(relation).to_a
+ end
end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 766b2ff2ef..954eab8022 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -46,4 +46,9 @@ class SanitizeTest < ActiveRecord::TestCase
select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts])
assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables')
end
+
+ def test_sanitize_sql_array_handles_empty_statement
+ select_author_sql = Post.send(:sanitize_sql_array, [''])
+ assert_equal('', select_author_sql)
+ end
end
diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb
index 35af9f679b..6fc270673f 100644
--- a/activerecord/test/models/electron.rb
+++ b/activerecord/test/models/electron.rb
@@ -1,3 +1,5 @@
class Electron < ActiveRecord::Base
belongs_to :molecule
+
+ validates_presence_of :name
end
diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb
index 69325b8d29..26870c8f88 100644
--- a/activerecord/test/models/molecule.rb
+++ b/activerecord/test/models/molecule.rb
@@ -1,4 +1,6 @@
class Molecule < ActiveRecord::Base
belongs_to :liquid
has_many :electrons
+
+ accepts_nested_attributes_for :electrons
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 9a62bd5a77..713bb3c1e2 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,26 @@
+* Added `Object#present_in` to simplify value whitelisting.
+
+ Before:
+
+ params[:bucket_type].in?(%w( project calendar )) ? params[:bucket_type] : nil
+
+ After:
+
+ params[:bucket_type].present_in %w( project calendar )
+
+ *DHH*
+
+* Time helpers honor the application time zone when passed a date.
+
+ *Xavier Noria*
+
+* Fix the implementation of Multibyte::Unicode.tidy_bytes for JRuby
+
+ The existing implementation caused JRuby to raise the error:
+ `Encoding::ConverterNotFoundError: code converter not found (UTF-8 to UTF8-MAC)`
+
+ *Justin Coyne*
+
* Fix `to_param` behavior when there are nested empty hashes.
Before:
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 3b5e205244..f3f8416905 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -41,6 +41,8 @@ module Kernel
# end
#
# puts 'But this will'
+ #
+ # This method is not thread-safe.
def silence_stream(stream)
old_stream = stream.dup
stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
@@ -100,6 +102,8 @@ module Kernel
# Silences both STDOUT and STDERR, even for subprocesses.
#
# quietly { system 'bundle install' }
+ #
+ # This method is not thread-safe.
def quietly
silence_stream(STDOUT) do
silence_stream(STDERR) do
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index db07d549b0..67f0e0335d 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -27,7 +27,8 @@ class Module
def attr_internal_define(attr_name, type)
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
- class_eval do # class_eval is necessary on 1.9 or else the methods a made private
+ # class_eval is necessary on 1.9 or else the methods are made private
+ class_eval do
# use native attr_* methods as they are faster on some Ruby implementations
send("attr_#{type}", internal_name)
end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index b5671f66d0..141f19e7b3 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -12,4 +12,16 @@ class Object
rescue NoMethodError
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
end
+
+ # Returns the receiver if it's included in the argument otherwise returns +nil+.
+ # Argument must be any object which responds to +#include?+. Usage:
+ #
+ # params[:bucket_type].present_in %w( project calendar )
+ #
+ # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ #
+ # @return [Object]
+ def present_in(another_object)
+ self.in?(another_object) ? self : nil
+ end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 6be19771f5..59675d744e 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -407,7 +407,8 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- # to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false
+ # to_s works around a ruby1.9 issue where String#starts_with?(Pathname)
+ # will raise a TypeError: no implicit conversion of Pathname into String
autoload_once_paths.any? { |base| path.starts_with? base.to_s }
end
@@ -665,6 +666,14 @@ module ActiveSupport #:nodoc:
constants = normalized.split('::')
to_remove = constants.pop
+ # Remove the file path from the loaded list.
+ file_path = search_for_file(const.underscore)
+ if file_path
+ expanded = File.expand_path(file_path)
+ expanded.sub!(/\.rb\z/, '')
+ self.loaded.delete(expanded)
+ end
+
if constants.empty?
parent = Object
else
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index 4ea6abfa12..2ca1124e76 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,5 +1,11 @@
require 'active_support/inflector/inflections'
+#--
+# Defines the standard inflection rules. These are the starting point for
+# new projects and are not considered complete. The current set of inflection
+# rules is frozen. This means, we do not change them to become more complete.
+# This is a safety measure to keep existing applications from breaking.
+#++
module ActiveSupport
Inflector.inflections(:en) do |inflect|
inflect.plural(/$/, 's')
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 84799c2399..ea3cdcd024 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -213,7 +213,7 @@ module ActiveSupport
end
# Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
- if RUBY_VERSION >= '2.1'
+ if '<3'.respond_to?(:scrub)
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
# resulting in a valid UTF-8 string.
#
@@ -233,16 +233,16 @@ module ActiveSupport
# We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
# CP1252 when we get errors. The final string will be 'converted' back to UTF-8
# before returning.
- reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
+ reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE)
source = string.dup
- out = ''.force_encoding(Encoding::UTF_8_MAC)
+ out = ''.force_encoding(Encoding::UTF_16LE)
loop do
reader.primitive_convert(source, out)
_, _, _, error_bytes, _ = reader.primitive_errinfo
break if error_bytes.nil?
- out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace)
+ out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace)
end
reader.finish
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 9e0a3d6345..eefa84262e 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -10,7 +10,7 @@ module ActiveSupport
def stub_object(object, method_name, return_value)
key = [object.object_id, method_name]
- if (stub = @stubs[key])
+ if stub = @stubs[key]
unstub_object(stub)
end
@@ -61,14 +61,23 @@ module ActiveSupport
travel_to Time.now + duration, &block
end
- # Changes current time to the given time by stubbing +Time.now+ and +Date.today+ to return the
- # time or date passed into this method.
+ # Changes current time to the given time by stubbing +Time.now+ and
+ # +Date.today+ to return the time or date passed into this method.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# Date.current # => Wed, 24 Nov 2004
#
+ # Dates are taken as their timestamp at the beginning of the day in the
+ # application time zone. <tt>Time.current</tt> returns said timestamp,
+ # and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
+ # <tt>Date.current</tt> returns a date equal to the argument, and
+ # <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
+ # be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
+ # or <tt>Date.today</tt>, in order to honor the application time zone
+ # please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
+ #
# This method also accepts a block, which will return the current time back to its original
# state at the end of the block:
#
@@ -78,12 +87,21 @@ module ActiveSupport
# end
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
def travel_to(date_or_time, &block)
- simple_stubs.stub_object(Time, :now, date_or_time.to_time)
- simple_stubs.stub_object(Date, :today, date_or_time.to_date)
+ if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
+ now = date_or_time.midnight.to_time
+ else
+ now = date_or_time.to_time
+ end
+
+ simple_stubs.stub_object(Time, :now, now)
+ simple_stubs.stub_object(Date, :today, now.to_date)
if block_given?
- block.call
- travel_back
+ begin
+ block.call
+ ensure
+ travel_back
+ end
end
end
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index b3f0e7198d..b9d6417b07 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,7 +1,7 @@
module ActiveSupport
# Returns the version of the currently loaded ActiveSupport as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta1"
+ Gem::Version.new "4.1.0.beta2"
end
module VERSION #:nodoc:
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 478706eeae..c5e2cc693a 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -47,4 +47,9 @@ class InTest < ActiveSupport::TestCase
def test_no_method_catching
assert_raise(ArgumentError) { 1.in?(1) }
end
+
+ def test_present_in
+ assert_equal "stuff", "stuff".present_in(%w( lots of stuff ))
+ assert_nil "stuff".present_in(%w( lots of crap ))
+ end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 00bec5bd9d..4ca63b3417 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -948,6 +948,18 @@ class DependenciesTest < ActiveSupport::TestCase
Object.class_eval { remove_const :A if const_defined?(:A) }
end
+ def test_access_unloaded_constants_for_reload
+ with_autoloading_fixtures do
+ assert_kind_of Module, A
+ assert_kind_of Class, A::B # Necessary to load A::B for the test
+ ActiveSupport::Dependencies.mark_for_unload(A::B)
+ ActiveSupport::Dependencies.remove_unloadable_constants!
+
+ A::B # Make sure no circular dependency error
+ end
+ end
+
+
def test_autoload_once_paths_should_behave_when_recursively_loading
with_loading 'dependencies', 'autoloading_fixtures' do
ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last]
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index cd79efbe8c..79ec57af2b 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -124,6 +124,32 @@ class TimeZoneTest < ActiveSupport::TestCase
travel_back
end
+ def test_travel_to_a_date
+ with_env_tz do
+ Time.use_zone('Hawaii') do
+ date = Date.new(2014, 2, 18)
+ time = date.midnight
+
+ travel_to date do
+ assert_equal date, Date.current
+ assert_equal time, Time.current
+ end
+ end
+ end
+ end
+
+ def test_travel_to_travels_back_and_reraises_if_the_block_raises
+ ts = Time.current - 1.second
+
+ travel_to ts do
+ raise
+ end
+
+ flunk # ensure travel_to re-raises
+ rescue
+ assert_not_equal ts, Time.current
+ end
+
def test_local
time = ActiveSupport::TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45)
assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 90e6b2fcbc..a859553b1b 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -346,9 +346,11 @@ for detailed changes.
params "deep munging" that was used to address security vulnerability
CVE-2013-0155. ([Pull Request](https://github.com/rails/rails/pull/13188))
-* Added `:serializer` option for `config.session_store :cookie_store`. This
- changes default serializer when using
- `:cookie_store`. ([Pull Request](https://github.com/rails/rails/pull/13692))
+* New config option `config.action_dispatch.cookies_serializer` for specifying
+ a serializer for the signed and encrypted cookie jars. (Pull Requests [1](https://github.com/rails/rails/pull/13692), [2](https://github.com/rails/rails/pull/13945) / [More Details](upgrading_ruby_on_rails.html#cookies-serializer))
+
+* Added `render :plain`, `render :html` and `render :body`. ([Pull Request](https://github.com/rails/rails/pull/14062) /
+ [More Details](upgrading_ruby_on_rails.html#rendering-content-from-string))
Action Mailer
@@ -455,6 +457,12 @@ for detailed changes.
### Notable changes
+* Default scopes are no longer overriden by chained conditions.
+
+ Before this change when you defined a `default_scope` in a model
+ it was overriden by chained conditions in the same field. Now it
+ is merged like any other scope. [More Details](upgrading_ruby_on_rails.html#changes-on-default-scopes).
+
* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from
a model's attribute or
method. ([Pull Request](https://github.com/rails/rails/pull/12891))
@@ -541,6 +549,9 @@ for detailed changes.
* Make `change_column_null`
revertable. ([Commit](https://github.com/rails/rails/commit/724509a9d5322ff502aefa90dd282ba33a281a96))
+* Added a flag to disable schema dump after migration. This is set to `false`
+ by defualt in the production environment for new applications. ([Pull Request](https://github.com/rails/rails/pull/13948))
+
Active Model
------------
@@ -658,9 +669,8 @@ for detailed changes.
[More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
* Added `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These
- methods change current time to the given time or time difference by stubbing
- `Time.now` and
- `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824))
+ methods change current time to the given time or duration by stubbing
+ `Time.now` and `Date.today`.
* Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method returns
the current time to the original state, by removing the stubs added by `travel`
@@ -699,6 +709,8 @@ for detailed changes.
responsibilities within a
class. ([Commit](https://github.com/rails/rails/commit/1eee0ca6de975b42524105a59e0521d18b38ab81))
+* Added `Object#present_in` to simplify value whitelisting. ([Commit](https://github.com/rails/rails/commit/4edca106daacc5a159289eae255207d160f22396))
+
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 27c53689c4..9fd930963a 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -15,7 +15,7 @@
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.16/">http://guides.rubyonrails.org/v3.2.16/</a>.
+ The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.17/">http://guides.rubyonrails.org/v3.2.17/</a>.
</p>
<p>
The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 9eaf03dd82..5b5f53c9be 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -381,22 +381,6 @@ You can also pass a `:domain` key and specify the domain name for the cookie:
YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
```
-You can pass `:serializer` key to specify serializer for serializing session:
-
-```ruby
-YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :json
-```
-
-The default serializer for new application is `:json`. For compatibility with
-old applications `:marshal` is used when `serializer` option is not specified.
-
-It is also possible to pass a custom serializer class with `load` and `dump`
-public methods defined:
-
-```ruby
-YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: MyCustomSerializer
-```
-
Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/initializers/secret_token.rb`
```ruby
@@ -588,6 +572,38 @@ end
Note that while for session values you set the key to `nil`, to delete a cookie value you should use `cookies.delete(:key)`.
+Rails also provides a signed cookie jar and an encrypted cookie jar for storing
+sensitive data. The signed cookie jar appends a cryptographic signature on the
+cookie values to protect their integrity. The encrypted cookie jar encrypts the
+values in addition to signing them, so that they cannot be read by the end user.
+Refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Cookies.html)
+for more details.
+
+These special cookie jars use a serializer to serialize the assigned values into
+strings and deserializes them into Ruby objects on read.
+
+You can specify what serializer to use:
+
+```ruby
+Rails.application.config.action_dispatch.cookies_serializer = :json
+```
+
+The default serializer for new applications is `:json`. For compatibility with
+old applications with existing cookies, `:marshal` is used when `serializer`
+option is not specified.
+
+You may also set this option to `:hybrid`, in which case Rails would transparently
+deserialize existing (`Marshal`-serialized) cookies on read and re-write them in
+the `JSON` format. This is useful for migrating existing applications to the
+`:json` serializer.
+
+It is also possible to pass a custom serializer that responds to `load` and
+`dump`:
+
+```ruby
+Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer
+```
+
Rendering XML and JSON data
---------------------------
@@ -1072,7 +1088,7 @@ class ApplicationController < ActionController::Base
private
def record_not_found
- render text: "404 Not Found", status: 404
+ render plain: "404 Not Found", status: 404
end
end
```
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index d164b08d93..4900f176a6 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -707,7 +707,7 @@ You can additionally unscope specific where clauses. For example:
```ruby
Post.where(id: 10, trashed: false).unscope(where: :id)
-# => SELECT "posts".* FROM "posts" WHERE trashed = 0
+# SELECT "posts".* FROM "posts" WHERE trashed = 0
```
A relation which has used `unscope` will affect any relation it is
@@ -715,7 +715,7 @@ merged in to:
```ruby
Post.order('id asc').merge(Post.unscope(:order))
-# => SELECT "posts".* FROM "posts"
+# SELECT "posts".* FROM "posts"
```
### `only`
@@ -1242,26 +1242,26 @@ class User < ActiveRecord::Base
end
User.active.inactive
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
```
We can mix and match `scope` and `where` conditions and the final sql
-will have all conditions joined with `AND` .
+will have all conditions joined with `AND`.
```ruby
User.active.where(state: 'finished')
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
```
If we do want the `last where clause` to win then `Relation#merge` can
-be used .
+be used.
```ruby
User.active.merge(User.inactive)
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
```
-One important caveat is that `default_scope` will be overridden by
+One important caveat is that `default_scope` will be prepended in
`scope` and `where` conditions.
```ruby
@@ -1272,16 +1272,16 @@ class User < ActiveRecord::Base
end
User.all
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
User.active
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
User.where(state: 'inactive')
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
```
-As you can see above the `default_scope` is being overridden by both
+As you can see above the `default_scope` is being merged in both
`scope` and `where` conditions.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 0ea2b86bb4..a483a6dd24 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -577,7 +577,7 @@ so it may happen that two different database connections create two records
with the same value for a column that you intend to be unique. To avoid that,
you must create a unique index on both columns in your database. See
[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html)
-for more details about multi column indexes.
+for more details about multiple column indexes.
```ruby
class Account < ActiveRecord::Base
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 59dfefd22f..2ad09f599b 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1403,6 +1403,8 @@ The third argument, `indent_empty_lines`, is a flag that says whether empty line
The `indent!` method performs indentation in-place.
+NOTE: Defined in `active_support/core_ext/string/indent.rb`.
+
### Access
#### `at(position)`
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 7466b4ff48..261538d0be 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -172,7 +172,7 @@ def empty?
end
```
-The API is careful not to commit to any particular value, the predicate has
+The API is careful not to commit to any particular value, the method has
predicate semantics, that's enough.
Filenames
@@ -215,6 +215,13 @@ ordinary method names, symbols, paths (with forward slashes), etc. Please use
`<tt>...</tt>` for everything else, notably class or module names with a
namespace as in `<tt>ActiveRecord::Base</tt>`.
+You can quickly test the RDoc output with the following command:
+
+```
+$ echo "+:to_param+" | rdoc --pipe
+#=> <p><code>:to_param</code></p>
+```
+
### Regular Font
When "true" and "false" are English words rather than Ruby keywords use a regular font:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 0422dda0d8..5bb895cb78 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -302,7 +302,7 @@ Sprockets uses files named `index` (with the relevant extensions) for a special
purpose.
For example, if you have a jQuery library with many modules, which is stored in
-`lib/assets/library_name`, the file `lib/assets/library_name/index.js` serves as
+`lib/assets/javascripts/library_name`, the file `lib/assets/javascripts/library_name/index.js` serves as
the manifest for all files in this library. This file could include a list of
all the required files in order, or a simple `require_tree` directive.
@@ -1018,7 +1018,8 @@ The X-Sendfile header is a directive to the web server to ignore the response
from the application, and instead serve a specified file from disk. This option
is off by default, but can be enabled if your server supports it. When enabled,
this passes responsibility for serving the file to the web server, which is
-faster.
+faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file)
+on how to use this feature.
Apache and nginx support this option, which can be enabled in
`config/environments/production.rb`:
@@ -1033,6 +1034,10 @@ option, take care to paste this configuration option only into `production.rb`
and any other environments you define with production behavior (not
`application.rb`).
+TIP: For further details have a look at the docs of your production web server:
+- [Apache](https://tn123.org/mod_xsendfile/)
+- [Nginx](http://wiki.nginx.org/XSendfile)
+
Assets Cache Store
------------------
@@ -1145,7 +1150,7 @@ config.assets.digest = true
```
Rails 4 no longer sets default config values for Sprockets in `test.rb`, so
-`test.rb` now requies Sprockets configuration. The old defaults in the test
+`test.rb` now requires Sprockets configuration. The old defaults in the test
environment are: `config.assets.compile = true`, `config.assets.compress =
false`, `config.assets.debug = false` and `config.assets.digest = false`.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index fa136eda1e..7b72e27b96 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -473,7 +473,6 @@ There are a few configuration options available in Active Support:
* `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings.
-* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
### Configuring a Database
@@ -722,7 +721,7 @@ Rails will now prepend "/app1" when generating links.
#### Using Passenger
-Passenger makes it easily to run your application in a subdirectory. You can find
+Passenger makes it easy to run your application in a subdirectory. You can find
the relevant configuration in the
[passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri).
@@ -940,4 +939,4 @@ ActiveRecord::ConnectionTimeoutError - could not obtain a database connection wi
If you get the above error, you might want to increase the size of connection
pool by incrementing the `pool` option in `database.yml`
-NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
+NOTE. As Rails is multi-threaded by default, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index af2305f1b7..36e3862c6b 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -136,7 +136,7 @@ You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` als
The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
-As of this writing (December, 2010) they are especially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
+If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
```bash
$ RUBYOPT=-W0 bundle exec rake test
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index bdb1a61bfb..a16b9ac8da 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -21,19 +21,22 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer
-* The [RubyGems](http://rubygems.org) packaging system
-* To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org)
-* A working installation of the [SQLite3 Database](http://www.sqlite.org)
+* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer.
+* The [RubyGems](http://rubygems.org) packaging system, which is installed with Ruby
+ versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org).
+* A working installation of the [SQLite3 Database](http://www.sqlite.org).
Rails is a web application framework running on the Ruby programming language.
If you have no prior experience with Ruby, you will find a very steep learning
-curve diving straight into Rails. There are some good free resources on the
-Internet for learning Ruby, including:
+curve diving straight into Rails. There are several curated lists of online resources
+for learning Ruby:
-* [Mr. Neighborly's Humble Little Ruby Book](http://www.humblelittlerubybook.com)
-* [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)
-* [Why's (Poignant) Guide to Ruby](http://mislav.uniqpath.com/poignant-guide/)
+* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/)
+* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby)
+
+Be aware that some resources, while still excellent, cover versions of Ruby as old as
+1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day
+development with Rails.
What is Rails?
--------------
@@ -54,11 +57,13 @@ learned elsewhere, you may have a less happy experience.
The Rails philosophy includes two major guiding principles:
-* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and
- over again is a bad thing.
-* Convention Over Configuration - means that Rails makes assumptions about what
- you want to do and how you're going to do it, rather than requiring you to
- specify every little thing through endless configuration files.
+* **Don't Repeat Yourself:** DRY is a principle of software development which
+ states that "Every piece of knowledge must have a single, unambiguous, authoritative
+ representation within a system." By not writing the same information over and over
+ again, our code is more maintainable, more extensible, and less buggy.
+* **Convention Over Configuration:** Rails has opinions about the best way to do many
+ things in a web application, and defaults to this set of conventions, rather than
+ require that you specify every minutiae through endless configuration files.
Creating a New Rails Project
----------------------------
@@ -73,9 +78,9 @@ By following along with this guide, you'll create a Rails project called
(very) simple weblog. Before you can start building the application, you need to
make sure that you have Rails itself installed.
-TIP: The examples below use `#` and `$` to denote superuser and regular
-user terminal prompts respectively in a UNIX-like OS. If you are using
-Windows, your prompt will look something like `c:\source_code>`
+TIP: The examples below use `$` to represent your terminal prompt in a UNIX-like OS,
+though it may have been customized to appear differently. If you are using Windows,
+your prompt will look something like `c:\source_code>`
### Installing Rails
@@ -97,6 +102,16 @@ If you don't have Ruby installed have a look at
[ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to
install Ruby on your platform.
+Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows
+users and others can find installation instructions at [the SQLite3 website](http://www.sqlite.org).
+Verify that it is correctly installed and in your PATH:
+
+```bash
+$ sqlite3 --version
+```
+
+The program should report its version.
+
To install Rails, use the `gem install` command provided by RubyGems:
```bash
@@ -147,20 +162,20 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
-|app|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|bin|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
+|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
+|bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
-|db|Contains your current database schema, as well as the database migrations.|
+|db/|Contains your current database schema, as well as the database migrations.|
|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com).|
-|lib|Extended modules for your application.|
-|log|Application log files.|
-|public|The only folder seen by the world as-is. Contains static files and compiled assets.|
+|lib/|Extended modules for your application.|
+|log/|Application log files.|
+|public/|The only folder seen by the world as-is. Contains static files and compiled assets.|
|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
-|test|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
-|tmp|Temporary files (like cache, pid, and session files).|
-|vendor|A place for all third-party code. In a typical Rails application this includes vendored gems.|
+|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
+|tmp/|Temporary files (like cache, pid, and session files).|
+|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
Hello, Rails!
-------------
@@ -367,12 +382,11 @@ styling for it afterwards.
### Laying down the ground work
-The first thing that you are going to need to create a new article within the
-application is a place to do that. A great place for that would be at
-`/articles/new`.
-With the route already defined, requests can now be made to `/articles/new` in
-the application. Navigate to <http://localhost:3000/articles/new> and you'll see
-a routing error:
+Firstly, you need a place within the application to create a new article. A
+great place for that would be at `/articles/new`. With the route already
+defined, requests can now be made to `/articles/new` in the application.
+Navigate to <http://localhost:3000/articles/new> and you'll see a routing
+error:
![Another routing error, uninitialized constant ArticlesController](images/getting_started/routing_error_no_controller.png)
@@ -594,7 +608,7 @@ look like, change the `create` action to this:
```ruby
def create
- render text: params[:article].inspect
+ render plain: params[:article].inspect
end
```
@@ -881,9 +895,9 @@ Let's add links to the other views as well, starting with adding this
```
This link will allow you to bring up the form that lets you create a new article.
-You should also add a link to this template - `app/views/articles/new.html.erb`
-- to go back to the `index` action. Do this by adding this underneath the form
-in this template:
+
+Also add a link in `app/views/articles/new.html.erb`, underneath the form, to
+go back to the `index` action:
```erb
<%= form_for :article do |f| %>
@@ -1105,7 +1119,11 @@ The `method: :patch` option tells Rails that we want this form to be submitted
via the `PATCH` HTTP method which is the HTTP method you're expected to use to
**update** resources according to the REST protocol.
-TIP: By default forms built with the _form_for_ helper are sent via `POST`.
+The first parameter of the `form_tag` can be an object, say, `@article` which would
+cause the helper to fill in the form with the fields of the object. Passing in a
+symbol (`:article`) with the same name as the instance variable (`@article`) also
+automagically leads to the same behavior. This is what is happening here. More details
+can be found in [form_for documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for).
Next we need to create the `update` action in
`app/controllers/articles_controller.rb`:
@@ -1376,7 +1394,9 @@ class CreateComments < ActiveRecord::Migration
create_table :comments do |t|
t.string :commenter
t.text :body
- t.references :article, index: true
+
+ # this line adds an integer column called `article_id`.
+ t.references :article, index: true
t.timestamps
end
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 5e2e0ad3e3..ec3cec5c6f 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -270,7 +270,7 @@ def parse_options(args)
options = default_options
# Don't evaluate CGI ISINDEX parameters.
- # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
+ # http://www.meb.uni-bonn.de/docs/cgi/cl.html
args.clear if ENV.include?("REQUEST_METHOD")
options.merge! opt_parser.parse! args
@@ -522,7 +522,7 @@ I18n and Rails configuration are all being defined here.
### Back to `config/environment.rb`
The rest of `config/application.rb` defines the configuration for the
-`Rails::Application` which will be used once the application is fully
+`Rails::Application` which will be used once the application is fully
initialized. When `config/application.rb` has finished loading Rails and defined
the application namespace, we go back to `config/environment.rb`,
where the application is initialized. For example, if the application was called
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 0513066f5a..1ac4e7f40c 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -4,7 +4,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c72b584ed6..66ed6f2e08 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -236,15 +236,34 @@ render inline: "xml.p {'Horrid coding practice!'}", type: :builder
#### Rendering Text
-You can send plain text - with no markup at all - back to the browser by using the `:text` option to `render`:
+You can send plain text - with no markup at all - back to the browser by using
+the `:plain` option to `render`:
```ruby
-render text: "OK"
+render plain: "OK"
```
-TIP: Rendering pure text is most useful when you're responding to Ajax or web service requests that are expecting something other than proper HTML.
+TIP: Rendering pure text is most useful when you're responding to Ajax or web
+service requests that are expecting something other than proper HTML.
-NOTE: By default, if you use the `:text` option, the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the `layout: true` option.
+NOTE: By default, if you use the `:plain` option, the text is rendered without
+using the current layout. If you want Rails to put the text into the current
+layout, you need to add the `layout: true` option.
+
+#### Rendering HTML
+
+You can send a HTML string back to the browser by using the `:html` option to
+`render`:
+
+```ruby
+render html: "<strong>Not Found</strong>".html_safe
+```
+
+TIP: This is useful when you're rendering a small snippet of HTML code.
+However, you might want to consider moving it to a template file if the markup
+is complex.
+
+NOTE: This option will escape HTML entities if the string is not html safe.
#### Rendering JSON
@@ -276,6 +295,19 @@ render js: "alert('Hello Rails');"
This will send the supplied string to the browser with a MIME type of `text/javascript`.
+#### Rendering raw body
+
+You can send a raw content back to the browser, without setting any content
+type, by using the `:body` option to `render`:
+
+```ruby
+render body: "raw"
+```
+
+TIP: This option should be used only if you explicitly want the content type to
+be unset. Using `:plain` or `:html` might be more appropriate in most of the
+time.
+
#### Options for `render`
Calls to the `render` method generally accept four options:
@@ -1119,11 +1151,11 @@ With this change, you can access an instance of the `@products` collection as th
You can also pass in arbitrary local variables to any partial you are rendering with the `locals: {}` option:
```erb
-<%= render partial: "products", collection: @products,
+<%= render partial: "product", collection: @products,
as: :item, locals: {title: "Products Page"} %>
```
-Would render a partial `_products.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`.
+In this case, the partial will have access to a local variable `title` with the value "Products Page".
TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option.
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 70d4722068..9c495bf09d 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -631,7 +631,7 @@ This will define a `user_path` method that will be available in controllers, hel
### HTTP Verb Constraints
-In general, you should use the `get`, `post`, `put` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once:
+In general, you should use the `get`, `post`, `put`, `patch` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once:
```ruby
match 'photos', to: 'photos#show', via: [:get, :post]
diff --git a/guides/source/security.md b/guides/source/security.md
index 70fb066b64..ece431dae7 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -549,7 +549,7 @@ Injection is very tricky, because the same code or parameter can be malicious in
### Whitelists versus Blacklists
-NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._
+NOTE: _When sanitizing, protecting or verifying something, prefer whitelists over blacklists._
A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_:
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 408c072575..aa37115d14 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -248,7 +248,7 @@ To see how a test failure is reported, you can add a failing test to the `post_t
```ruby
test "should not save post without title" do
post = Post.new
- assert !post.save
+ assert_not post.save
end
```
@@ -272,7 +272,7 @@ In the output, `F` denotes a failure. You can see the corresponding trace shown
```ruby
test "should not save post without title" do
post = Post.new
- assert !post.save, "Saved the post without a title"
+ assert_not post.save, "Saved the post without a title"
end
```
@@ -401,8 +401,8 @@ Rails adds some custom assertions of its own to the `test/unit` framework:
| `assert_no_difference(expressions, message = nil, &amp;block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
| `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
| `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
-| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range|
-| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.|
+| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.|
+| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
| `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.|
You'll see the usage of some of these assertions in the next chapter.
@@ -518,8 +518,10 @@ You also have access to three instance variables in your functional tests:
### Setting Headers and CGI variables
-Headers and cgi variables can be set directly on the `@request`
-instance variable:
+[HTTP headers](http://tools.ietf.org/search/rfc2616#section-5.3)
+and
+[CGI variables](http://tools.ietf.org/search/rfc3875#section-4.1)
+can be set directly on the `@request` instance variable:
```ruby
# setting a HTTP Header
@@ -941,7 +943,7 @@ class UserMailerTest < ActionMailer::TestCase
# Send the email, then test that it got queued
email = UserMailer.create_invite('me@example.com',
'friend@example.com', Time.now).deliver
- assert !ActionMailer::Base.deliveries.empty?
+ assert_not ActionMailer::Base.deliveries.empty?
# Test the body of the sent email contains what we expect it to
assert_equal ['me@example.com'], email.from
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 2055452935..da124e21a4 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -98,6 +98,19 @@ If your test helper contains a call to
is now done automatically when you `require 'test_help'`, although
leaving this line in your helper is not harmful in any way.
+### Cookies serializer
+
+Applications created before Rails 4.1 uses `Marshal` to serialize cookie values into
+the signed and encrypted cookie jars. If you want to use the new `JSON`-based format
+in your application, you can add an initializer file with the following content:
+
+ ```ruby
+ Rails.application.config.cookies_serializer :hybrid
+ ```
+
+This would transparently migrate your existing `Marshal`-serialized cookies into the
+new `JSON`-based format.
+
### Changes in JSON handling
There are a few major changes related to JSON handling in Rails 4.1.
@@ -249,6 +262,92 @@ authors = Author.where(name: 'Hank Moody').to_a
authors.compact!
```
+### Changes on Default Scopes
+
+Default scopes are no longer overriden by chained conditions.
+
+In previous versions when you defined a `default_scope` in a model
+it was overriden by chained conditions in the same field. Now it
+is merged like any other scope.
+
+Before:
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+User.where(state: 'inactive')
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+```
+
+After:
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
+
+User.where(state: 'inactive')
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
+```
+
+To get the previous behavior it is needed to explicitly remove the
+`default_scope` condition using `unscoped`, `unscope`, `rewhere` or
+`except`.
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { unscope(where: :state).where(state: 'active') }
+ scope :inactive, -> { rewhere state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+User.inactive
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+```
+
+### Rendering content from string
+
+Rails 4.1 introduces `:plain`, `:html`, and `:body` options to `render`. Those
+options are now the preferred way to render string-based content, as it allows
+you to specify which content type you want the response sent as.
+
+* `render :plain` will set the content type to `text/plain`
+* `render :html` will set the content type to `text/html`
+* `render :body` will *not* set the content type header.
+
+From the security standpoint, if you don't expect to have any markup in your
+response body, you should be using `render :plain` as most browsers will escape
+unsafe content in the response for you.
+
+We will be deprecating the use of `render :text` in a future version. So please
+start using the more precise `:plain:`, `:html`, and `:body` options instead.
+Using `render :text` may pose a security risk, as the content is sent as
+`text/html`.
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
@@ -554,9 +653,8 @@ Active Record Observer and Action Controller Sweeper have been extracted to the
### sprockets-rails
-* `assets:precompile:primary` has been removed. Use `assets:precompile` instead.
-* The `config.assets.compress` option should be changed to
-`config.assets.js_compressor` like so for instance:
+* `assets:precompile:primary` and `assets:precompile:all` have been removed. Use `assets:precompile` instead.
+* The `config.assets.compress` option should be changed to `config.assets.js_compressor` like so for instance:
```ruby
config.assets.js_compressor = :uglifier
@@ -564,14 +662,14 @@ config.assets.js_compressor = :uglifier
### sass-rails
-* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`
+* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`.
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
-The following changes are meant for upgrading your application to Rails 3.2.16,
+The following changes are meant for upgrading your application to Rails 3.2.17,
the last 3.2.x version of Rails.
### Gemfile
@@ -579,7 +677,7 @@ the last 3.2.x version of Rails.
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.16'
+gem 'rails', '3.2.17'
group :assets do
gem 'sass-rails', '~> 3.2.6'
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 3c04204c29..a8695ec034 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -180,7 +180,7 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out:
$(document).ready ->
$("#new_post").on("ajax:success", (e, data, status, xhr) ->
$("#new_post").append xhr.responseText
- ).bind "ajax:error", (e, xhr, status, error) ->
+ ).on "ajax:error", (e, xhr, status, error) ->
$("#new_post").append "<p>ERROR</p>"
```
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index a57d56f4aa..bb3cc1760e 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Do not crash when `config/secrets.yml` is empty.
+
+ *Yves Senn*
+
* Set `dump_schema_after_migration` config values in production.
Set `config.active_record.dump_schema_after_migration` as false
@@ -21,13 +25,6 @@
*Cristian Mircea Messel*, *Chulki Lee*
-* Only lookup `config.log_level` for stdlib `::Logger` instances.
- Assign it as is for third party loggers like `Log4r::Logger`.
-
- Fixes #13421.
-
- *Yves Senn*
-
* The `Gemfile` of new applications depends on SDoc ~> 0.4.0.
*Xavier Noria*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 36432e56ba..e37347b576 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -206,7 +206,7 @@ module Rails
"action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt,
"action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
"action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
- "action_dispatch.session_serializer" => config.session_options[:serializer]
+ "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer
})
end
end
@@ -308,7 +308,8 @@ module Rails
yaml = config.paths["config/secrets"].first
if File.exist?(yaml)
require "erb"
- env_secrets = YAML.load(ERB.new(IO.read(yaml)).result)[Rails.env]
+ all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
+ env_secrets = all_secrets[Rails.env]
secrets.merge!(env_secrets.symbolize_keys) if env_secrets
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 33bcab1e57..a26d41c0cf 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -53,11 +53,7 @@ INFO
logger
end
- if ::Logger === Rails.logger
- Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
- else
- Rails.logger.level = config.log_level
- end
+ Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
end
# Initialize cache early in the stack so railties can make use of it.
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 55709b80ae..f1f79d8378 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -14,7 +14,6 @@ module Rails
DATABASES.concat(JDBC_DATABASES)
attr_accessor :rails_template
- attr_accessor :app_template
add_shebang_option!
argument :app_path, type: :string
@@ -27,9 +26,6 @@ module Rails
class_option :template, type: :string, aliases: '-m',
desc: "Path to some #{name} template (can be a filesystem path or URL)"
- class_option :app_template, type: :string, aliases: '-n',
- desc: "Path to some #{name} template (can be a filesystem path or URL)"
-
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
@@ -126,10 +122,6 @@ module Rails
}.curry[@gem_filter]
end
- def remove_gem(name)
- add_gem_entry_filter { |gem| gem.name != name }
- end
-
def builder
@builder ||= begin
builder_class = get_builder_class
@@ -149,92 +141,21 @@ module Rails
FileUtils.cd(destination_root) unless options[:pretend]
end
- class TemplateRecorder < ::BasicObject # :nodoc:
- attr_reader :gems
-
- def initialize(target)
- @target = target
- # unfortunately, instance eval has access to these ivars
- @app_const = target.send :app_const if target.respond_to?(:app_const, true)
- @app_const_base = target.send :app_const_base if target.respond_to?(:app_const_base, true)
- @app_name = target.send :app_name if target.respond_to?(:app_name, true)
- @commands = []
- @gems = []
- end
-
- def gemfile_entry(*args)
- @target.send :gemfile_entry, *args
- end
-
- def add_gem_entry_filter(*args, &block)
- @target.send :add_gem_entry_filter, *args, &block
- end
-
- def remove_gem(*args, &block)
- @target.send :remove_gem, *args, &block
- end
-
- def method_missing(name, *args, &block)
- @commands << [name, args, block]
- end
-
- def respond_to_missing?(method, priv = false)
- super || @target.respond_to?(method, priv)
- end
-
- def replay!
- @commands.each do |name, args, block|
- @target.send name, *args, &block
- end
- end
- end
-
def apply_rails_template
- @recorder = TemplateRecorder.new self
-
- apply(rails_template, target: self) if rails_template
- apply(app_template, target: @recorder) if app_template
+ apply rails_template if rails_template
rescue Thor::Error, LoadError, Errno::ENOENT => e
raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}"
end
- def replay_template
- @recorder.replay! if @recorder
- end
-
- def apply(path, config={})
- verbose = config.fetch(:verbose, true)
- target = config.fetch(:target, self)
- is_uri = path =~ /^https?\:\/\//
- path = find_in_source_paths(path) unless is_uri
-
- say_status :apply, path, verbose
- shell.padding += 1 if verbose
-
- if is_uri
- contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
- else
- contents = open(path) {|io| io.read }
- end
-
- target.instance_eval(contents, path)
- shell.padding -= 1 if verbose
- end
-
def set_default_accessors!
self.destination_root = File.expand_path(app_path, destination_root)
- self.rails_template = expand_template options[:template]
- self.app_template = expand_template options[:app_template]
- end
-
- def expand_template(name)
- case name
- when /^https?:\/\//
- name
- when String
- File.expand_path(name, Dir.pwd)
- else
- name
+ self.rails_template = case options[:template]
+ when /^https?:\/\//
+ options[:template]
+ when String
+ File.expand_path(options[:template], Dir.pwd)
+ else
+ options[:template]
end
end
@@ -412,7 +333,8 @@ module Rails
require 'bundler'
Bundler.with_clean_env do
- print `"#{Gem.ruby}" "#{_bundle_command}" #{command}`
+ output = `"#{Gem.ruby}" "#{_bundle_command}" #{command}`
+ print output unless options[:quiet]
end
end
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
index 8bb7c2b768..b5045671b3 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
@@ -1,5 +1,5 @@
<h1><%= class_name %>#<%= @action %></h1>
<p>
- <%%= @greeting %>, find me in app/views/<%= @path %>
+ <%%= @greeting %>, find me in <%= @path %>
</p>
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
index 6d597256a6..342285df19 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
@@ -1,3 +1,3 @@
<%= class_name %>#<%= @action %>
-<%%= @greeting %>, find me in app/views/<%= @path %>
+<%%= @greeting %>, find me in <%= @path %>
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index d2eca5b2fb..83cb1dc0d5 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -166,7 +166,6 @@ module Rails
end
public_task :set_default_accessors!
- public_task :apply_rails_template
public_task :create_root
def create_root_files
@@ -236,8 +235,7 @@ module Rails
build(:leftovers)
end
- public_task :run_bundle
- public_task :replay_template
+ public_task :apply_rails_template, :run_bundle
public_task :generate_spring_binstubs
protected
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
new file mode 100644
index 0000000000..7a06a89f0f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
@@ -0,0 +1,3 @@
+# Be sure to restart your server when you modify this file.
+
+Rails.application.config.action_dispatch.cookies_serializer = :json \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index 097fcb4bb0..2bb9b82c61 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>, serializer: :json
+Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index dbe1e37d8e..f6f529b80a 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -185,7 +185,6 @@ task default: :test
end
public_task :set_default_accessors!
- public_task :apply_rails_template
public_task :create_root
def create_root_files
@@ -242,6 +241,7 @@ task default: :test
build(:leftovers)
end
+ public_task :apply_rails_template, :run_bundle
def name
@name ||= begin
@@ -255,9 +255,6 @@ task default: :test
end
end
- public_task :run_bundle
- public_task :replay_template
-
protected
def app_templates_dir
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 923cab4e2a..e4fd798d18 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 4
MINOR = 1
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index b814479540..b39cd3747b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -336,6 +336,14 @@ module ApplicationTests
assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key
end
+ test "blank config/secrets.yml does not crash the loading process" do
+ app_file 'config/secrets.yml', <<-YAML
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_nil app.secrets.not_defined
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -754,7 +762,7 @@ module ApplicationTests
end
end
- test "lookup config.log_level with custom logger (stdlib Logger)" do
+ test "config.log_level with custom logger" do
make_basic_app do |app|
app.config.logger = Logger.new(STDOUT)
app.config.log_level = :info
@@ -762,19 +770,6 @@ module ApplicationTests
assert_equal Logger::INFO, Rails.logger.level
end
- test "assign log_level as is with custom logger (third party logger)" do
- logger_class = Class.new do
- attr_accessor :level
- end
- logger_instance = logger_class.new
- make_basic_app do |app|
- app.config.logger = logger_instance
- app.config.log_level = :info
- end
- assert_equal logger_instance, Rails.logger
- assert_equal :info, Rails.logger.level
- end
-
test "respond_to? accepts include_private" do
make_basic_app
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 700935fd8d..5811379e35 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -163,73 +163,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_arbitrary_code
- output = Tempfile.open('my_template') do |template|
- template.puts 'puts "You are using Rails version #{Rails::VERSION::STRING}."'
- template.close
- run_generator([destination_root, "-m", template.path])
- end
- assert_match 'You are using', output
- end
-
- def test_add_gemfile_entry
- Tempfile.open('my_template') do |template|
- template.puts 'gemfile_entry "tenderlove"'
- template.flush
- template.close
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile", /tenderlove/
- end
- end
-
- def test_add_skip_entry
- Tempfile.open 'my_template' do |template|
- template.puts 'add_gem_entry_filter { |gem| gem.name != "jbuilder" }'
- template.close
-
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'jbuilder', contents
- end
- end
- end
-
- def test_remove_gem
- Tempfile.open 'my_template' do |template|
- template.puts 'remove_gem "jbuilder"'
- template.close
-
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'jbuilder', contents
- end
- end
- end
-
- def test_skip_turbolinks_when_it_is_not_on_gemfile
- Tempfile.open 'my_template' do |template|
- template.puts 'add_gem_entry_filter { |gem| gem.name != "turbolinks" }'
- template.flush
-
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'turbolinks', contents
- end
-
- assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_no_match 'turbolinks', contents
- end
-
- assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_no_match('data-turbolinks-track', contents)
- end
-
- assert_file "app/assets/javascripts/application.js" do |contents|
- assert_no_match 'turbolinks', contents
- end
- end
- end
-
def test_config_another_database
run_generator([destination_root, "-d", "mysql"])
assert_file "config/database.yml", /mysql/
@@ -433,7 +366,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_new_hash_style
run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file|
- assert_match(/config.session_store :cookie_store, key: '_.+_session', serializer: :json/, file)
+ assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
end
end
diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb
index 94d2c1bf50..7871399dd7 100644
--- a/railties/test/generators/generator_test.rb
+++ b/railties/test/generators/generator_test.rb
@@ -1,7 +1,6 @@
require 'active_support/test_case'
require 'active_support/testing/autorun'
require 'rails/generators/app_base'
-require 'rails/generators/rails/app/app_generator'
module Rails
module Generators
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index d209801f60..25649881eb 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -69,12 +69,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_text_template_engine
run_generator
assert_file "app/views/notifier/foo.text.erb" do |view|
- assert_match(%r(app/views/notifier/foo\.text\.erb), view)
+ assert_match(%r(\sapp/views/notifier/foo\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
assert_file "app/views/notifier/bar.text.erb" do |view|
- assert_match(%r(app/views/notifier/bar\.text\.erb), view)
+ assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
end
@@ -82,12 +82,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_html_template_engine
run_generator
assert_file "app/views/notifier/foo.html.erb" do |view|
- assert_match(%r(app/views/notifier/foo\.html\.erb), view)
+ assert_match(%r(\sapp/views/notifier/foo\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
assert_file "app/views/notifier/bar.html.erb" do |view|
- assert_match(%r(app/views/notifier/bar\.html\.erb), view)
+ assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
end
diff --git a/version.rb b/version.rb
index 923cab4e2a..e4fd798d18 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 4
MINOR = 1
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end