aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile7
-rw-r--r--actionmailer/actionmailer.gemspec17
-rw-r--r--actionpack/CHANGELOG.md27
-rw-r--r--actionpack/actionpack.gemspec28
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb15
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb160
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb8
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb5
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb23
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb110
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb4
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb2
-rw-r--r--actionpack/lib/action_view/path_set.rb22
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb5
-rw-r--r--actionpack/test/controller/flash_test.rb6
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb6
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb37
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb2
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb2
-rw-r--r--actionpack/test/dispatch/cookies_test.rb32
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb5
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb5
-rw-r--r--actionpack/test/template/date_helper_test.rb5
-rw-r--r--actionpack/test/template/form_helper_test.rb13
-rw-r--r--actionpack/test/template/template_test.rb17
-rw-r--r--activemodel/CHANGELOG.md4
-rw-r--r--activemodel/activemodel.gemspec14
-rw-r--r--activemodel/lib/active_model/errors.rb4
-rw-r--r--activemodel/lib/active_model/forbidden_attributes_protection.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb4
-rw-r--r--activemodel/lib/active_model/railtie.rb4
-rw-r--r--activemodel/lib/active_model/secure_password.rb5
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb40
-rw-r--r--activemodel/test/cases/railtie_test.rb28
-rw-r--r--activemodel/test/cases/secure_password_test.rb13
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb20
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb2
-rw-r--r--activerecord/CHANGELOG.md34
-rw-r--r--activerecord/activerecord.gemspec21
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb10
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb15
-rw-r--r--activerecord/lib/active_record/core.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb8
-rw-r--r--activerecord/lib/active_record/null_relation.rb6
-rw-r--r--activerecord/lib/active_record/railtie.rb33
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb25
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb24
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb10
-rw-r--r--activerecord/lib/active_record/timestamp.rb3
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb17
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb26
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb13
-rw-r--r--activerecord/test/cases/calculations_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb8
-rw-r--r--activerecord/test/cases/finder_test.rb5
-rw-r--r--activerecord/test/cases/migration/index_test.rb16
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb4
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb21
-rw-r--r--activerecord/test/models/topic.rb7
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activesupport/CHANGELOG.md30
-rw-r--r--activesupport/activesupport.gemspec19
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/hash/diff.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb37
-rw-r--r--activesupport/lib/active_support/core_ext/struct.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb56
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb6
-rw-r--r--activesupport/lib/active_support/json/encoding.rb2
-rw-r--r--activesupport/lib/active_support/key_generator.rb54
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb7
-rw-r--r--activesupport/lib/active_support/test_case.rb8
-rw-r--r--activesupport/lib/active_support/testing/mocha_module.rb22
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb4
-rw-r--r--activesupport/lib/active_support/xml_mini.rb38
-rw-r--r--activesupport/test/autoloading_fixtures/should_not_be_required.rb1
-rw-r--r--activesupport/test/constantize_test_cases.rb88
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/kernel_test.rb2
-rw-r--r--activesupport/test/core_ext/struct_test.rb10
-rw-r--r--activesupport/test/dependencies_test.rb10
-rw-r--r--activesupport/test/json/encoding_test.rb18
-rw-r--r--activesupport/test/message_encryptor_test.rb2
-rw-r--r--activesupport/test/spec_type_test.rb3
-rw-r--r--activesupport/test/test_case_test.rb3
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb2
-rw-r--r--activesupport/test/xml_mini_test.rb62
-rw-r--r--guides/assets/stylesheets/main.css104
-rw-r--r--guides/code/getting_started/config/initializers/secret_token.rb2
-rw-r--r--guides/rails_guides/markdown/renderer.rb2
-rw-r--r--guides/source/4_0_release_notes.md39
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md38
-rw-r--r--guides/source/working_with_javascript_in_rails.md4
-rw-r--r--rails.gemspec22
-rw-r--r--railties/CHANGELOG.md9
-rw-r--r--railties/lib/rails/application.rb61
-rw-r--r--railties/lib/rails/application/configuration.rb4
-rw-r--r--railties/lib/rails/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails/commands/server.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt4
-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_new/plugin_new_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb2
-rw-r--r--railties/lib/rails/rack/logger.rb4
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/railties.gemspec31
-rw-r--r--railties/test/abstract_unit.rb1
-rw-r--r--railties/test/application/assets_test.rb26
-rw-r--r--railties/test/application/configuration_test.rb12
-rw-r--r--railties/test/application/initializers/frameworks_test.rb4
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb4
-rw-r--r--railties/test/application/middleware/session_test.rb159
-rw-r--r--railties/test/application/rake_test.rb29
-rw-r--r--railties/test/application/url_generation_test.rb2
-rw-r--r--railties/test/commands/console_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb2
-rw-r--r--railties/test/generators/namespaced_generators_test.rb30
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb6
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb9
-rw-r--r--railties/test/generators/scaffold_generator_test.rb20
-rw-r--r--railties/test/isolation/abstract_unit.rb4
160 files changed, 1785 insertions, 655 deletions
diff --git a/Gemfile b/Gemfile
index b9b3a4cb23..cb8f85b0a3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,7 +4,7 @@ gemspec
gem 'arel', github: 'rails/arel', branch: 'master'
-gem 'mocha', '>= 0.11.2', require: false
+gem 'mocha', '~> 0.13.0', require: false
gem 'rack-test', github: 'brynary/rack-test'
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.0.0'
@@ -27,7 +27,7 @@ group :doc do
# to a bug, but the PR that fixes it has been there
# for some weeks unapplied. As a temporary solution
# this is our own fork with the fix.
- gem 'sdoc', github: 'fxn/sdoc'
+ gem 'sdoc', github: 'voloko/sdoc'
gem 'redcarpet', '~> 2.2.2', platforms: :ruby
gem 'w3c_validators'
end
@@ -42,7 +42,8 @@ instance_eval File.read local_gemfile if File.exists? local_gemfile
platforms :mri do
group :test do
gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0'
- gem 'debugger' if !ENV['TRAVIS'] && RUBY_VERSION < '2.0'
+ gem 'debugger' if !ENV['TRAVIS'] && RUBY_VERSION < '2.0' && RUBY_PATCHLEVEL < 327
+
end
end
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 0c669e2e91..0177a13e50 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -6,17 +6,20 @@ Gem::Specification.new do |s|
s.version = version
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
+
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
- s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.4.4')
+ s.add_dependency 'actionpack', version
+
+ s.add_dependency 'mail', '~> 2.4.4'
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 0ffae5086e..1ebc75ed2f 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,32 @@
## Rails 4.0.0 (unreleased) ##
+* Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list
+ of mime types where template text is not html escaped by default. It prevents `Jack & Joe`
+ from rendering as `Jack &amp; Joe` for the whitelisted mime types. The default whitelist
+ contains text/plain. Fix #7976
+
+ *Joost Baaij*
+
+* Fix input name when `:multiple => true` and `:index` are set.
+
+ Before:
+
+ check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
+
+ After:
+
+ check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
+
+ Fix #8108
+
+ *Daniel Fox, Grant Hutchins & Trace Wax*
+
+* Clear url helpers when reloading routes.
+
+ *Santiago Pastorino*
+
* `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's
returned value if any. Fix #8086
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 7d292ac17c..89fdd528c2 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -6,24 +6,26 @@ Gem::Specification.new do |s|
s.version = version
s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.'
+
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
- s.add_dependency('activesupport', version)
- s.add_dependency('builder', '~> 3.1.0')
- s.add_dependency('rack', '~> 1.4.1')
- s.add_dependency('rack-test', '~> 0.6.1')
- s.add_dependency('journey', '~> 2.0.0')
- s.add_dependency('erubis', '~> 2.7.0')
+ s.add_dependency 'activesupport', version
+ s.add_dependency 'builder', '~> 3.1.0'
+ s.add_dependency 'rack', '~> 1.4.1'
+ s.add_dependency 'rack-test', '~> 0.6.1'
+ s.add_dependency 'journey', '~> 2.0.0'
+ s.add_dependency 'erubis', '~> 2.7.0'
- s.add_development_dependency('activemodel', version)
- s.add_development_dependency('tzinfo', '~> 0.3.33')
+ s.add_development_dependency 'activemodel', version
+ s.add_development_dependency 'tzinfo', '~> 0.3.33'
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 5705ab590c..02ac111392 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -29,13 +29,14 @@ module AbstractController
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action
def _normalize_callback_options(options)
- if only = options[:only]
- only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[:if] = Array(options[:if]) << only
- end
- if except = options[:except]
- except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
- options[:unless] = Array(options[:unless]) << except
+ _normalize_callback_option(options, :only, :if)
+ _normalize_callback_option(options, :except, :unless)
+ end
+
+ def _normalize_callback_option(options, from, to) # :nodoc:
+ if from = options[from]
+ from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
+ options[to] = Array(options[to]) << from
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 9b3bf99fc3..971c4189c8 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,4 +1,5 @@
require "action_controller/log_subscriber"
+require "action_controller/metal/params_wrapper"
module ActionController
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 6d46586367..d3b5bafee1 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -249,9 +249,9 @@ module ActionController
end
def secret_token(request)
- secret = request.env["action_dispatch.secret_token"]
- raise "You must set config.secret_token in your app's config" if secret.blank?
- secret
+ key_generator = request.env["action_dispatch.key_generator"]
+ http_auth_salt = request.env["action_dispatch.http_auth_salt"]
+ key_generator.generate_key(http_auth_salt)
end
# Uses an MD5 digest based on time to generate a value to be used only once.
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 09abc999c1..a475d4bdff 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
+require 'active_support/core_ext/struct'
require 'action_dispatch/http/mime_types'
module ActionController
@@ -72,12 +73,99 @@ module ActionController
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
+ require 'mutex_m'
+
+ class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
+ include Mutex_m
+
+ def self.from_hash(hash)
+ name = hash[:name]
+ format = Array(hash[:format])
+ include = hash[:include] && Array(hash[:include]).collect(&:to_s)
+ exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
+ new name, format, include, exclude, nil, nil
+ end
+
+ def initialize(name, format, include, exclude, klass, model) # nodoc
+ super
+ @include_set = include
+ @name_set = name
+ end
+
+ def model
+ super || synchronize { super || self.model = _default_wrap_model }
+ end
+
+ def include
+ return super if @include_set
+
+ m = model
+ synchronize do
+ return super if @include_set
+
+ @include_set = true
+
+ unless super || exclude
+ if m.respond_to?(:attribute_names) && m.attribute_names.any?
+ self.include = m.attribute_names
+ end
+ end
+ end
+ end
+
+ def name
+ return super if @name_set
+
+ m = model
+ synchronize do
+ return super if @name_set
+
+ @name_set = true
+
+ unless super || klass.anonymous?
+ self.name = m ? m.to_s.demodulize.underscore :
+ klass.controller_name.singularize
+ end
+ end
+ end
+
+ private
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singularize name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model #:nodoc:
+ return nil if klass.anonymous?
+ model_name = klass.name.sub(/Controller$/, '').classify
+
+ begin
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
+ namespaces = model_name.split("::")
+ namespaces.delete_at(-2)
+ break if namespaces.last == model_name
+ model_name = namespaces.join("::")
+ end
+ end until model_klass
+
+ model_klass
+ end
+ end
+
included do
class_attribute :_wrapper_options
- self._wrapper_options = { :format => [] }
+ self._wrapper_options = Options.from_hash(format: [])
end
module ClassMethods
+ def _set_wrapper_options(options)
+ self._wrapper_options = Options.from_hash(options)
+ end
+
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
# would use to determine the attribute names from.
#
@@ -119,68 +207,24 @@ module ActionController
model = name_or_model_or_options
end
- _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
+ opts.model = model
+ opts.klass = self
+
+ self._wrapper_options = opts
end
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Will be called automatically when the
# module is inherited.
def inherited(klass)
- if klass._wrapper_options[:format].present?
- klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
+ if klass._wrapper_options.format.any?
+ params = klass._wrapper_options.dup
+ params.klass = klass
+ klass._wrapper_options = params
end
super
end
-
- protected
-
- # Determine the wrapper model from the controller's name. By convention,
- # this could be done by trying to find the defined model that has the
- # same singularize name as the controller. For example, +UsersController+
- # will try to find if the +User+ model exists.
- #
- # This method also does namespace lookup. Foo::Bar::UsersController will
- # try to find Foo::Bar::User, Foo::User and finally User.
- def _default_wrap_model #:nodoc:
- return nil if self.anonymous?
- model_name = self.name.sub(/Controller$/, '').classify
-
- begin
- if model_klass = model_name.safe_constantize
- model_klass
- else
- namespaces = model_name.split("::")
- namespaces.delete_at(-2)
- break if namespaces.last == model_name
- model_name = namespaces.join("::")
- end
- end until model_klass
-
- model_klass
- end
-
- def _set_wrapper_defaults(options, model=nil)
- options = options.dup
-
- unless options[:include] || options[:exclude]
- model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
- options[:include] = model.attribute_names
- end
- end
-
- unless options[:name] || self.anonymous?
- model ||= _default_wrap_model
- options[:name] = model ? model.to_s.demodulize.underscore :
- controller_name.singularize
- end
-
- options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
- options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
- options[:format] = Array(options[:format])
-
- self._wrapper_options = options
- end
end
# Performs parameters wrapping upon the request. Will be called automatically
@@ -205,20 +249,20 @@ module ActionController
# Returns the wrapper key which will use to stored wrapped parameters.
def _wrapper_key
- _wrapper_options[:name]
+ _wrapper_options.name
end
# Returns the list of enabled formats.
def _wrapper_formats
- _wrapper_options[:format]
+ _wrapper_options.format
end
# Returns the list of parameters which will be selected for wrapped.
def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options[:include]
+ value = if include_only = _wrapper_options.include
parameters.slice(*include_only)
else
- exclude = _wrapper_options[:exclude] || []
+ exclude = _wrapper_options.exclude || []
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index a50f0ca8c1..265ce5d6f3 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -121,11 +121,11 @@ module ActionController #:nodoc:
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
- secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
- host = request.host
- secure = request.ssl?
+ key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
+ host = request.host
+ secure = request.ssl?
- new(secret, host, secure)
+ new(key_generator, host, secure)
end
def write(*)
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 04dc1d37f7..da640502a2 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -65,7 +65,6 @@ module ActionController
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
cattr_accessor :permit_all_parameters, instance_accessor: false
- attr_accessor :permitted # :nodoc:
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
@@ -260,7 +259,9 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- self.class.new(super)
+ self.class.new(super).tap do |new_instance|
+ new_instance.instance_variable_set :@permitted, @permitted
+ end
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 0ec355246e..1d716a3248 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -81,10 +81,12 @@ module ActionDispatch
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- 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 :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :UpgradeSignatureToEncryptionCookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 2b5d3d85bf..f56f09c5b3 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -288,18 +288,23 @@ module Mime
@@html_types.include?(to_sym) || @string =~ /html/
end
+
private
- def method_missing(method, *args)
- if method.to_s.ends_with? '?'
- method[0..-2].downcase.to_sym == to_sym
- else
- super
- end
- end
- def respond_to_missing?(method, include_private = false) #:nodoc:
- method.to_s.ends_with? '?'
+ def to_ary; end
+ def to_a; end
+
+ def method_missing(method, *args)
+ if method.to_s.ends_with? '?'
+ method[0..-2].downcase.to_sym == to_sym
+ else
+ super
end
+ end
+
+ def respond_to_missing?(method, include_private = false) #:nodoc:
+ method.to_s.ends_with? '?'
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 8aa02ec482..9a7e8a5a9c 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -8,14 +8,16 @@ module ActionDispatch
class << self
def extract_domain(host, tld_length = @@tld_length)
- return nil unless named_host?(host)
- host.split('.').last(1 + tld_length).join('.')
+ host.split('.').last(1 + tld_length).join('.') if named_host?(host)
end
def extract_subdomains(host, tld_length = @@tld_length)
- return [] unless named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length+2)]
+ if named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length + 2)]
+ else
+ []
+ end
end
def extract_subdomain(host, tld_length = @@tld_length)
@@ -23,15 +25,13 @@ module ActionDispatch
end
def url_for(options = {})
- path = ""
- path << options.delete(:script_name).to_s.chomp("/")
+ path = options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s
params = options[:params] || {}
- params.reject! {|k,v| v.to_param.nil? }
+ params.reject! { |_,v| v.to_param.nil? }
result = build_host_url(options)
-
result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
result << "?#{params.to_query}" unless params.empty?
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index eaf922595a..2f148752cb 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/message_verifier'
module ActionDispatch
class Request < Rack::Request
@@ -27,7 +28,7 @@ module ActionDispatch
# 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>config.secret_token</tt> value.
+ # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
# # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
@@ -79,7 +80,11 @@ module ActionDispatch
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
class Cookies
- HTTP_HEADER = "Set-Cookie".freeze
+ HTTP_HEADER = "Set-Cookie".freeze
+ GENERATOR_KEY = "action_dispatch.key_generator".freeze
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
TOKEN_KEY = "action_dispatch.secret_token".freeze
# Raised when storing more than 4K of session data.
@@ -103,21 +108,28 @@ module ActionDispatch
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.build(request)
- secret = request.env[TOKEN_KEY]
+ env = request.env
+ key_generator = env[GENERATOR_KEY]
+ options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT],
+ encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT],
+ encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT],
+ token_key: env[TOKEN_KEY] }
+
host = request.host
secure = request.ssl?
- new(secret, host, secure).tap do |hash|
+ new(key_generator, host, secure, options).tap do |hash|
hash.update(request.cookies)
end
end
- def initialize(secret = nil, host = nil, secure = false)
- @secret = secret
+ def initialize(key_generator, host = nil, secure = false, options = {})
+ @key_generator = key_generator
@set_cookies = {}
@delete_cookies = {}
@host = host
@secure = secure
+ @options = options
@cookies = {}
end
@@ -220,7 +232,7 @@ module ActionDispatch
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
- @permanent ||= PermanentCookieJar.new(self, @secret)
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@@ -228,7 +240,7 @@ module ActionDispatch
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
#
@@ -237,7 +249,28 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||= SignedCookieJar.new(self, @secret)
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
+ def signed_using_old_secret #:nodoc:
+ @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options)
+ end
+
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
+ # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
+ # will be raised.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
end
def write(headers)
@@ -261,8 +294,10 @@ module ActionDispatch
end
class PermanentCookieJar < CookieJar #:nodoc:
- def initialize(parent_jar, secret)
- @parent_jar, @secret = parent_jar, secret
+ def initialize(parent_jar, key_generator, options = {})
+ @parent_jar = parent_jar
+ @key_generator = key_generator
+ @options = options
end
def []=(key, options)
@@ -283,11 +318,11 @@ module ActionDispatch
class SignedCookieJar < CookieJar #:nodoc:
MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
- SECRET_MIN_LENGTH = 30 # Characters
- def initialize(parent_jar, secret)
- ensure_secret_secure(secret)
+ 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)
end
@@ -314,26 +349,41 @@ module ActionDispatch
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
+ end
+
+ class EncryptedCookieJar < SignedCookieJar #:nodoc:
+ def initialize(parent_jar, key_generator, options = {})
+ if ActiveSupport::DummyKeyGenerator === key_generator
+ raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
+ "Set config.secret_key_base in config/initializers/secret_token.rb"
+ end
+
+ @parent_jar = parent_jar
+ @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)
+ end
- protected
-
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
- def ensure_secret_secure(secret)
- if secret.blank?
- raise ArgumentError, "A secret is required to generate an " +
- "integrity hash for cookie session data. Use " +
- "config.secret_token = \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/initializers/secret_token.rb"
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ @encryptor.decrypt_and_verify(encrypted_message)
end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature,
+ ActiveSupport::MessageVerifier::InvalidMessage
+ nil
+ end
- if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " +
- "like \"#{SecureRandom.hex(16)}\". The value you " +
- "provided, \"#{secret}\", is shorter than the minimum length " +
- "of #{SECRET_MIN_LENGTH} characters"
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ else
+ options = { :value => options }
end
+ options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ @parent_jar[key] = options
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 3f28ea75ef..d7f83a1cc6 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -57,8 +57,7 @@ module ActionDispatch
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
- request = ActionDispatch::Request.new(env)
- if data = request.cookie_jar.signed[@key]
+ if data = get_cookie(env)
data.stringify_keys!
end
data || {}
@@ -72,8 +71,43 @@ module ActionDispatch
end
def set_cookie(env, session_id, cookie)
+ cookie_jar(env)[@key] = cookie
+ end
+
+ def get_cookie(env)
+ cookie_jar(env)[@key]
+ end
+
+ def cookie_jar(env)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar.signed
+ end
+ end
+
+ class EncryptedCookieStore < CookieStore
+
+ private
+
+ def cookie_jar(env)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar.encrypted
+ end
+ end
+
+ # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
+ #
+ # To use this CookieStore set MyApp.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
+ # in your config/initializers/session_store.rb
+ class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
+ private
+
+ def get_cookie(env)
+ signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
+ end
+
+ def signed_using_old_secret_cookie_jar(env)
request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed[@key] = cookie
+ request.cookie_jar.signed_using_old_secret
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 0de10695e0..2b37a8d026 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -28,7 +28,7 @@ module ActionDispatch
def call(env)
begin
- response = @app.call(env)
+ response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 284dd180db..98c87d9b2d 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -13,6 +13,10 @@ module ActionDispatch
config.action_dispatch.rescue_responses = { }
config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = false
+ config.action_dispatch.http_auth_salt = 'http authentication'
+ config.action_dispatch.signed_cookie_salt = 'signed cookie'
+ config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
+ config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index cbc9c0f493..d6fe436b68 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -496,9 +496,7 @@ module ActionDispatch
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
- prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
- prefix = '' if prefix == '/'
- prefix
+ _routes.url_helpers.send("#{name}_path", prefix_options)
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index ecaac84057..d70063d0e9 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -98,7 +98,7 @@ module ActionDispatch
# Redirect any path to another path:
#
- # get '/stories', to: redirect('/posts')
+ # get "/stories" => redirect("/posts")
#
# You can also use interpolation in the supplied redirect argument:
#
@@ -112,11 +112,11 @@ module ActionDispatch
# return value.
#
# get 'jokes/:number', to: redirect { |params, request|
- # path = (params[:number].to_i.even? ? 'wheres-the-beef' : 'i-love-lamp')
+ # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
# }
#
- # Note that the <tt>do end</tt> syntax for the redirect block wouldn't work, as Ruby would pass
+ # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
# the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
@@ -129,7 +129,7 @@ module ActionDispatch
# common redirect routes. The call method must accept two arguments, params and request, and return
# a string.
#
- # get 'accounts/:name', to: redirect(SubdomainRedirector.new('api'))
+ # get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
options = args.extract_options!
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 61071d1228..0f95daa790 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -288,6 +288,7 @@ module ActionDispatch
def clear!
@finalized = false
+ @url_helpers = nil
named_routes.clear
set.clear
formatter.clear
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 305bafc0c5..8f17ee05be 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,5 +1,4 @@
require 'uri'
-require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/indifferent_access'
require 'action_controller/metal/exceptions'
@@ -44,9 +43,8 @@ module ActionDispatch
expected_options.stringify_keys!
- # FIXME: minitest does object diffs, do we need to have our own?
message ||= sprintf("The recognized options <%s> did not match <%s>, difference: <%s>",
- request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
+ request.path_parameters, expected_options, diff(expected_options, request.path_parameters))
assert_equal(expected_options, request.path_parameters, message)
end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 459f95bb73..6e51ba66a5 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -129,7 +129,7 @@ module ActionView
minutes_with_offset = distance_in_minutes
end
remainder = (minutes_with_offset % 525600)
- distance_in_years = (minutes_with_offset / 525600)
+ distance_in_years = (minutes_with_offset.div 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index b7b3db959e..46ebe60ec2 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -152,7 +152,7 @@ module ActionView
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
- # In case if you don't want the helper to generate this hidden field you can specify <tt>include_blank: false</tt> option.
+ # In case if you don't want the helper to generate this hidden field you can specify <tt>include_hidden: false</tt> option.
#
def select(object, method, choices, options = {}, html_options = {})
Tags::Select.new(object, method, self, choices, options, html_options).render
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 7f42d6e9c3..e298751062 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -52,7 +52,7 @@ module ActionView
# <%= form_tag('/posts') do -%>
# <div><%= submit_tag 'Save' %></div>
# <% end -%>
- # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
+ # # => <form action="/posts" method="post"><div><input type="submit" name="commit" value="Save" /></div></form>
#
# <%= form_tag('/posts', remote: true) %>
# # => <form action="/posts" method="post" data-remote="true">
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
index 192f5eebaa..3d597079c4 100644
--- a/actionpack/lib/action_view/helpers/tags/base.rb
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -80,9 +80,11 @@ module ActionView
options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
- options["name"] ||= options.fetch("name"){ options['multiple'] ? tag_name_multiple : tag_name }
+ options["name"] ||= options.fetch("name"){ tag_name }
options["id"] = options.fetch("id"){ tag_id }
end
+
+ options["name"] += "[]" if options["multiple"]
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
@@ -90,10 +92,6 @@ module ActionView
"#{@object_name}[#{sanitized_method_name}]"
end
- def tag_name_multiple
- "#{tag_name}[]"
- end
-
def tag_name_with_index(index)
"#{@object_name}[#{index}][#{sanitized_method_name}]"
end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
index e23f5113fb..45f0bc3d7b 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -27,7 +27,7 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all check boxes are unchecked.
- hidden = @template_object.hidden_field_tag(tag_name_multiple, "", :id => nil)
+ hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil)
rendered_collection + hidden
end
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index bbb1af8154..d9c76366f8 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -5,6 +5,8 @@ module ActionView #:nodoc:
attr_reader :paths
+ delegate :[], :include?, :pop, :size, :each, to: :paths
+
def initialize(paths = [])
@paths = typecast paths
end
@@ -14,30 +16,10 @@ module ActionView #:nodoc:
self
end
- def [](i)
- paths[i]
- end
-
def to_ary
paths.dup
end
- def include?(item)
- paths.include? item
- end
-
- def pop
- paths.pop
- end
-
- def size
- paths.size
- end
-
- def each(&block)
- paths.each(&block)
- end
-
def compact
PathSet.new paths.compact
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index aa8eac7846..731d8f9dab 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -47,6 +47,10 @@ module ActionView
class_attribute :erb_implementation
self.erb_implementation = Erubis
+ # Do not escape templates of these mime types.
+ class_attribute :escape_whitelist
+ self.escape_whitelist = ["text/plain"]
+
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
def self.call(template)
@@ -78,6 +82,7 @@ module ActionView
self.class.erb_implementation.new(
erb,
+ :escape => (self.class.escape_whitelist.include? template.type),
:trim => (self.class.erb_trim_mode == "-")
).src
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 8340aab4d2..6414ba3994 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class FlashTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -217,7 +219,7 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
- SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+ Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
class TestController < ActionController::Base
add_flash_types :bar
@@ -291,7 +293,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
# Overwrite get to send SessionSecret in env hash
def get(path, parameters = nil, env = {})
- env["action_dispatch.secret_token"] ||= SessionSecret
+ env["action_dispatch.key_generator"] ||= Generator
super
end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index b11ad633bd..c4a94264c3 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
class DummyDigestController < ActionController::Base
@@ -40,8 +42,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
- @secret = "session_options_secret"
- @request.env["action_dispatch.secret_token"] = @secret
+ @secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret)
end
teardown do
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index fc63470174..7cc71fe6dc 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -20,26 +20,51 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
end
- test "permitted is sticky on accessors" do
+ test "not permitted is sticky on accessors" do
assert !@params.slice(:person).permitted?
assert !@params[:person][:name].permitted?
+ assert !@params[:person].except(:name).permitted?
- @params.each { |key, value| assert(value.permitted?) if key == :person }
+ @params.each { |key, value| assert(!value.permitted?) if key == "person" }
assert !@params.fetch(:person).permitted?
assert !@params.values_at(:person).first.permitted?
end
+ test "permitted is sticky on accessors" do
+ @params.permit!
+ assert @params.slice(:person).permitted?
+ assert @params[:person][:name].permitted?
+ assert @params[:person].except(:name).permitted?
+
+ @params.each { |key, value| assert(value.permitted?) if key == "person" }
+
+ assert @params.fetch(:person).permitted?
+
+ assert @params.values_at(:person).first.permitted?
+ end
+
+ test "not permitted is sticky on mutators" do
+ assert !@params.delete_if { |k| k == "person" }.permitted?
+ assert !@params.keep_if { |k,v| k == "person" }.permitted?
+ end
+
test "permitted is sticky on mutators" do
- assert !@params.delete_if { |k| k == :person }.permitted?
- assert !@params.keep_if { |k,v| k == :person }.permitted?
+ @params.permit!
+ assert @params.delete_if { |k| k == "person" }.permitted?
+ assert @params.keep_if { |k,v| k == "person" }.permitted?
end
- test "permitted is sticky beyond merges" do
+ test "not permitted is sticky beyond merges" do
assert !@params.merge(a: "b").permitted?
end
+ test "permitted is sticky beyond merges" do
+ @params.permit!
+ assert @params.merge(a: "b").permitted?
+ end
+
test "modifying the parameters" do
@params[:person][:hometown] = "Chicago"
@params[:person][:family] = { brother: "Jonas" }
@@ -77,7 +102,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
ActionController::Parameters.permit_all_parameters = false
end
end
-
+
test "permitting parameters as an array" do
assert_equal "32", @params[:person].permit([ :age ])[:age]
end
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 209f021cf7..d87e2b85b0 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -4,7 +4,7 @@ module Admin; class User; end; end
module ParamsWrapperTestHelp
def with_default_wrapper_options(&block)
- @controller.class._wrapper_options = {:format => [:json]}
+ @controller.class._set_wrapper_options({:format => [:json]})
@controller.class.inherited(@controller.class)
yield
end
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index ab1bd0e3b6..718d06ef38 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -104,7 +104,7 @@ module ShowExceptions
get '/', {}, 'HTTP_ACCEPT' => 'text/json'
assert_response :internal_server_error
assert_equal 'text/plain', response.content_type.to_s
-
+ ensure
@app.instance_variable_set(:@exceptions_app, @exceptions_app)
$stderr = STDERR
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 347b3b3b5a..ffa91d63c4 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -65,6 +67,11 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def set_encrypted_cookie
+ cookies.encrypted[:foo] = 'bar'
+ head :ok
+ end
+
def raise_data_overflow
cookies.signed[:foo] = 'bye!' * 1024
head :ok
@@ -146,7 +153,10 @@ class CookiesTest < ActionController::TestCase
def setup
super
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+ @request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.host = "www.nextangle.com"
end
@@ -296,6 +306,16 @@ class CookiesTest < ActionController::TestCase
assert_equal 45, @controller.send(:cookies).signed[:user_id]
end
+ def test_encrypted_cookie
+ get :set_encrypted_cookie
+ 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_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
get :set_signed_cookie
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
@@ -329,29 +349,29 @@ class CookiesTest < ActionController::TestCase
def test_raises_argument_error_if_missing_secret
assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil)
get :set_signed_cookie
}
assert_raise(ArgumentError, ''.inspect) {
- @request.env["action_dispatch.secret_token"] = ""
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("")
get :set_signed_cookie
}
end
def test_raises_argument_error_if_secret_is_probably_insecure
assert_raise(ArgumentError, "password".inspect) {
- @request.env["action_dispatch.secret_token"] = "password"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password")
get :set_signed_cookie
}
assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.secret_token"] = "secret"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret")
get :set_signed_cookie
}
assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.secret_token"] = "12345678901234567890123456789"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789")
get :set_signed_cookie
}
end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index cfbf970a37..113608ecf4 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -241,6 +241,11 @@ module TestGenerationPrefix
assert_equal "/something/", app_object.root_path
end
+ test "[OBJECT] generating application's route includes default_url_options[:trailing_slash]" do
+ RailsApplication.routes.default_url_options[:trailing_slash] = true
+ assert_equal "/awesome/blog/posts", engine_object.posts_path
+ end
+
test "[OBJECT] generating engine's route with url_for" do
path = engine_object.url_for(:controller => "inside_engine_generating",
:action => "show",
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 41fa036a92..1677dee524 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -1,9 +1,12 @@
require 'abstract_unit'
require 'stringio'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+ Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
@@ -330,7 +333,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
# Overwrite get to send SessionSecret in env hash
def get(path, parameters = nil, env = {})
- env["action_dispatch.secret_token"] ||= SessionSecret
+ env["action_dispatch.key_generator"] ||= Generator
super
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 8bd8eff3c0..f9ce63fcb0 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -19,6 +19,8 @@ class DateHelperTest < ActionView::TestCase
end
def assert_distance_of_time_in_words(from, to=nil)
+ Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+
to ||= from
# 0..1 minute with :include_seconds => true
@@ -121,6 +123,9 @@ class DateHelperTest < ActionView::TestCase
assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to)
assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true)
assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false)
+
+ ensure
+ Fixnum.send :public, :/
end
def test_distance_in_words
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index fbfc73deda..c730e3ab74 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -525,6 +525,19 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_check_box_with_multiple_behavior_and_index
+ @post.comment_ids = [2,3]
+ assert_dom_equal(
+ '<input name="post[foo][comment_ids][]" type="hidden" value="0" /><input id="post_foo_comment_ids_1" name="post[foo][comment_ids][]" type="checkbox" value="1" />',
+ check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ )
+ assert_dom_equal(
+ '<input name="post[bar][comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_bar_comment_ids_3" name="post[bar][comment_ids][]" type="checkbox" value="3" />',
+ check_box("post", "comment_ids", { :multiple => true, :index => "bar" }, 3)
+ )
+
+ end
+
def test_checkbox_disabled_disables_hidden_field
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" disabled="disabled"/><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 86ba5f3b4d..ed9d303158 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -1,3 +1,4 @@
+# encoding: US-ASCII
require "abstract_unit"
require "logger"
@@ -25,6 +26,10 @@ class TestERBTemplate < ActiveSupport::TestCase
"Hello"
end
+ def apostrophe
+ "l'apostrophe"
+ end
+
def partial
ActionView::Template.new(
"<%= @virtual_path %>",
@@ -47,7 +52,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
end
- def new_template(body = "<%= hello %>", details = {})
+ def new_template(body = "<%= hello %>", details = { format: :html })
ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details))
end
@@ -71,6 +76,16 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_equal "Hello", render
end
+ def test_basic_template_does_html_escape
+ @template = new_template("<%= apostrophe %>")
+ assert_equal "l&#39;apostrophe", render
+ end
+
+ def test_text_template_does_not_html_escape
+ @template = new_template("<%= apostrophe %>", format: :text)
+ assert_equal "l'apostrophe", render
+ end
+
def test_raw_template
@template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new)
assert_equal "<%= hello %>", render
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index aa42bf762f..133bb558a9 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* Use BCrypt's MIN_COST in the test environment for speedier tests when using `has_secure_pasword`.
+
+ *Brian Cardarella + Jeremy Kemper + Trevor Turk*
+
* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to
protect attributes from mass assignment when non-permitted attributes are passed.
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index be5d5d3ca8..51655fe3da 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -8,15 +8,17 @@ Gem::Specification.new do |s|
s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
- s.add_dependency('activesupport', version)
- s.add_dependency('builder', '~> 3.1.0')
+ s.add_dependency 'activesupport', version
+
+ s.add_dependency 'builder', '~> 3.1.0'
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 6882b59e26..c82d4f012c 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -308,7 +308,7 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be empty"]}
def add_on_empty(attributes, options = {})
- [attributes].flatten.each do |attribute|
+ Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
is_empty = value.respond_to?(:empty?) ? value.empty? : false
add(attribute, :empty, options) if value.nil? || is_empty
@@ -322,7 +322,7 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be blank"]}
def add_on_blank(attributes, options = {})
- [attributes].flatten.each do |attribute|
+ Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
add(attribute, :blank, options) if value.blank?
end
diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb
index 4c05b19cba..7468f95548 100644
--- a/activemodel/lib/active_model/forbidden_attributes_protection.rb
+++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb
@@ -16,7 +16,7 @@ module ActiveModel
module ForbiddenAttributesProtection # :nodoc:
protected
- def sanitize_for_mass_assignment(attributes, options = {})
+ def sanitize_for_mass_assignment(attributes)
if attributes.respond_to?(:permitted?) && !attributes.permitted?
raise ActiveModel::ForbiddenAttributesError
else
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index eb8265d8c6..264880eecd 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -263,10 +263,10 @@ module ActiveModel
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> post
+ # ActiveModel::Naming.singular_route_key(Blog::Post) #=> post
#
# # For shared engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
+ # ActiveModel::Naming.singular_route_key(Blog::Post) #=> blog_post
def self.singular_route_key(record_or_class)
model_name_from_record_or_class(record_or_class).singular_route_key
end
diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb
index 75cde900e3..1671eb7bd4 100644
--- a/activemodel/lib/active_model/railtie.rb
+++ b/activemodel/lib/active_model/railtie.rb
@@ -4,5 +4,9 @@ require "rails"
module ActiveModel
class Railtie < Rails::Railtie # :nodoc:
config.eager_load_namespaces << ActiveModel
+
+ initializer "active_model.secure_password" do
+ ActiveModel::SecurePassword.min_cost = Rails.env.test?
+ end
end
end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index f19c836bea..081a49f749 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -2,6 +2,8 @@ module ActiveModel
module SecurePassword
extend ActiveSupport::Concern
+ class << self; attr_accessor :min_cost; end
+
module ClassMethods
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
@@ -92,7 +94,8 @@ module ActiveModel
def password=(unencrypted_password)
unless unencrypted_password.blank?
@password = unencrypted_password
- self.password_digest = BCrypt::Password.create(unencrypted_password)
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST
+ self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
end
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 4651154934..1eb0716891 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -104,7 +104,7 @@ module ActiveModel
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
- defaults.merge!(:attributes => attributes)
+ defaults[:attributes] = attributes
validations.each do |key, options|
next unless options
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index c795dc9dcd..629b157fed 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -135,7 +135,7 @@ module ActiveModel
# and instead be made available through the +attributes+ reader.
def initialize(options)
@attributes = Array(options.delete(:attributes))
- raise ":attributes cannot be blank" if @attributes.empty?
+ raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
super
check_validity!
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 3bc0d58351..293ce07f4e 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -7,7 +7,7 @@ class ErrorsTest < ActiveModel::TestCase
@errors = ActiveModel::Errors.new(self)
end
- attr_accessor :name
+ attr_accessor :name, :age
attr_reader :errors
def validate!
@@ -201,5 +201,43 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.generate_message(:name, :blank)
}
end
+
+ test "add_on_empty generates message" do
+ person = Person.new
+ person.errors.expects(:generate_message).with(:name, :empty, {})
+ person.errors.add_on_empty :name
+ end
+
+ test "add_on_empty generates message for multiple attributes" do
+ person = Person.new
+ person.errors.expects(:generate_message).with(:name, :empty, {})
+ person.errors.expects(:generate_message).with(:age, :empty, {})
+ person.errors.add_on_empty [:name, :age]
+ end
+
+ test "add_on_empty generates message with custom default message" do
+ person = Person.new
+ person.errors.expects(:generate_message).with(:name, :empty, {:message => 'custom'})
+ person.errors.add_on_empty :name, :message => 'custom'
+ end
+
+ test "add_on_blank generates message" do
+ person = Person.new
+ person.errors.expects(:generate_message).with(:name, :blank, {})
+ person.errors.add_on_blank :name
+ end
+
+ test "add_on_blank generates message for multiple attributes" do
+ person = Person.new
+ person.errors.expects(:generate_message).with(:name, :blank, {})
+ person.errors.expects(:generate_message).with(:age, :blank, {})
+ person.errors.add_on_blank [:name, :age]
+ end
+
+ test "add_on_blank generates message with custom default message" do
+ person = Person.new
+ person.errors.expects(:generate_message).with(:name, :blank, {:message => 'custom'})
+ person.errors.add_on_blank :name, :message => 'custom'
+ end
end
diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb
new file mode 100644
index 0000000000..f89a288f8f
--- /dev/null
+++ b/activemodel/test/cases/railtie_test.rb
@@ -0,0 +1,28 @@
+require 'cases/helper'
+require 'active_support/testing/isolation'
+
+class RailtieTest < ActiveModel::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ require 'rails/all'
+
+ @app ||= Class.new(::Rails::Application).tap do |app|
+ app.config.eager_load = false
+ end
+ end
+
+ test 'secure password min_cost is false in the development environment' do
+ Rails.env = 'development'
+ @app.initialize!
+
+ assert_equal false, ActiveModel::SecurePassword.min_cost
+ end
+
+ test 'secure password min_cost is true in the test environment' do
+ Rails.env = 'test'
+ @app.initialize!
+
+ assert_equal true, ActiveModel::SecurePassword.min_cost
+ end
+end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 509e2fdbb5..c7e93370ec 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -63,10 +63,21 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.run_callbacks :create
end
end
-
+
test "Oauthed user can be created with blank digest" do
assert_nothing_raised do
@oauthed_user.run_callbacks :create
end
end
+
+ test "Password digest cost defaults to bcrypt default cost" do
+ @user.password = "secret"
+ assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost
+ end
+
+ test "Password digest cost can be set to bcrypt min cost to speed up tests" do
+ ActiveModel::SecurePassword.min_cost = true
+ @user.password = "secret"
+ assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost
+ end
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 4f8b7327c0..4c01b47608 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -21,26 +21,6 @@ class I18nValidationTest < ActiveModel::TestCase
I18n.backend = @old_backend
end
- def test_errors_add_on_empty_generates_message
- @person.errors.expects(:generate_message).with(:title, :empty, {})
- @person.errors.add_on_empty :title
- end
-
- def test_errors_add_on_empty_generates_message_with_custom_default_message
- @person.errors.expects(:generate_message).with(:title, :empty, {:message => 'custom'})
- @person.errors.add_on_empty :title, :message => 'custom'
- end
-
- def test_errors_add_on_blank_generates_message
- @person.errors.expects(:generate_message).with(:title, :blank, {})
- @person.errors.add_on_blank :title
- end
-
- def test_errors_add_on_blank_generates_message_with_custom_default_message
- @person.errors.expects(:generate_message).with(:title, :blank, {:message => 'custom'})
- @person.errors.add_on_blank :title, :message => 'custom'
- end
-
def test_full_message_encoding
I18n.backend.store_translations('en', :errors => {
:messages => { :too_short => '猫舌' }})
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 07c1bd0533..457f553661 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -151,7 +151,7 @@ class ValidatesWithTest < ActiveModel::TestCase
end
test "each validator expects attributes to be given" do
- assert_raise RuntimeError do
+ assert_raise ArgumentError do
Topic.validates_with(ValidatorPerEachAttribute)
end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1d43840573..580a580ba5 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,39 @@
## Rails 4.0.0 (unreleased) ##
+* `#pluck` can be used on a relation with `select` clause
+ Fix #7551
+
+ Example:
+
+ Topic.select([:approved, :id]).order(:id).pluck(:id)
+
+ *Yves Senn*
+
+* Do not create useless database transaction when building `has_one` association.
+
+ Example:
+
+ User.has_one :profile
+ User.new.build_profile
+
+ *Bogdan Gusiev*
+
+* :counter_cache option for `has_many` associations to support custom named counter caches.
+ Fix #7993
+
+ *Yves Senn*
+
+* Deprecate the possibility to pass a string as third argument of `add_index`.
+ Pass `unique: true` instead.
+
+ add_index(:users, :organization_id, unique: true)
+
+ *Rafael Mendonça França*
+
+* Raise an `ArgumentError` when passing an invalid option to `add_index`.
+
+ *Rafael Mendonça França*
+
* Fix `find_in_batches` crashing when IDs are strings and start option is not specified.
*Alexis Bernard*
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 53791d96ef..31ddb01123 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -8,21 +8,22 @@ Gem::Specification.new do |s|
s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.'
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
- s.extra_rdoc_files = %w( README.rdoc )
+ s.extra_rdoc_files = %w(README.rdoc)
s.rdoc_options.concat ['--main', 'README.rdoc']
- s.add_dependency('activesupport', version)
- s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 3.0.2')
+ s.add_dependency 'activesupport', version
+ s.add_dependency 'activemodel', version
- s.add_dependency('activerecord-deprecated_finders', '0.0.1')
+ s.add_dependency 'arel', '~> 3.0.2'
+ s.add_dependency 'activerecord-deprecated_finders', '0.0.1'
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7a59da091d..651b17920c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,6 +1,9 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/dependencies/autoload'
+require 'active_support/concern'
+require 'active_record/errors'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -1122,6 +1125,9 @@ module ActiveRecord
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
# the associated records.
+ # [:counter_cache]
+ # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
+ # when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 1b382f7285..fcdfc1e150 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,5 +1,8 @@
+require 'active_record/associations'
+
module ActiveRecord::Associations::Builder
class CollectionAssociation < Association #:nodoc:
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
def valid_options
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index ab8225460a..0d1bdd21ee 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end
def valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 54215cf88d..862ff201de 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -174,8 +174,6 @@ module ActiveRecord
# association, it will be used for the query. Otherwise, construct options and pass them with
# scope to the target class's +count+.
def count(column_name = nil, count_options = {})
- return 0 if owner.new_record?
-
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
if options[:counter_sql] || options[:finder_sql]
@@ -366,6 +364,16 @@ module ActiveRecord
record
end
+ def scope(opts = {})
+ scope = super()
+ scope.none! if opts.fetch(:nullify, true) && null_scope?
+ scope
+ end
+
+ def null_scope?
+ owner.new_record? && !foreign_key_present?
+ end
+
private
def custom_counter_sql
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index e73f940334..e444b0ed83 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,10 +28,12 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
+ delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+
def initialize(association) #:nodoc:
@association = association
super association.klass, association.klass.arel_table
- merge! association.scope
+ merge! association.scope(nullify: false)
end
def target
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 74864d271f..f59565ae77 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -76,7 +76,7 @@ module ActiveRecord
end
def cached_counter_attribute_name(reflection = reflection)
- "#{reflection.name}_count"
+ options[:counter_cache] || "#{reflection.name}_count"
end
def update_counter(difference, reflection = reflection)
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 06bead41de..ee816d2392 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# If target and record are nil, or target is equal to record,
# we don't need to have transaction.
if (target || record) && target != record
- reflection.klass.transaction do
+ transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
if record
@@ -90,6 +90,14 @@ module ActiveRecord
def nullify_owner_attributes(record)
record[reflection.foreign_key] = nil
end
+
+ def transaction_if(value)
+ if value
+ reflection.klass.transaction { yield }
+ else
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 437fd00948..e0bfdb8f3e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -369,14 +369,10 @@ module ActiveRecord
end
def typecasted_attribute_value(name)
- if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- # FIXME: we need @attributes to be used consistently.
- # If the values stored in @attributes were already typecasted, this code
- # could be simplified
- read_attribute(name)
- end
+ # FIXME: we need @attributes to be used consistently.
+ # If the values stored in @attributes were already typecasted, this code
+ # could be simplified
+ read_attribute(name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 5b9ed81424..47d4a938af 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -5,7 +5,7 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for
- # serialization as keys and their class restriction as values.
+ # serialization as keys and their class restriction as values.
class_attribute :serialized_attributes, instance_accessor: false
self.serialized_attributes = {}
end
@@ -129,6 +129,14 @@ module ActiveRecord
end
end
end
+
+ def typecasted_attribute_value(name)
+ if self.class.serialized_attributes.include?(name)
+ @attributes[name].serialized_value
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index a028e04044..5eacb8f143 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -8,7 +8,6 @@ require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
@@ -333,7 +332,6 @@ module ActiveRecord #:nodoc:
extend Translation
extend DynamicMatchers
extend Explain
- extend ConnectionHandling
include Persistence
include ReadonlyAttributes
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 1da95f451f..db0db272a6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,7 +1,7 @@
require 'thread'
require 'monitor'
require 'set'
-require 'active_support/core_ext/module/deprecation'
+require 'active_support/deprecation'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -494,10 +494,18 @@ module ActiveRecord
@class_to_pool = Hash.new { |h,k| h[k] = {} }
end
- def connection_pools
+ def connection_pool_list
owner_to_pool.values.compact
end
+ def connection_pools
+ ActiveSupport::Deprecation.warn(
+ "In the next release, this will return the same as #connection_pool_list. " \
+ "(An array of pools, rather than a hash mapping specs to pools.)"
+ )
+ Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
+ end
+
def establish_connection(owner, spec)
@class_to_pool.clear
owner_to_pool[owner] = ConnectionAdapters::ConnectionPool.new(spec)
@@ -506,23 +514,23 @@ module ActiveRecord
# Returns true if there are any active connections among the connection
# pools that the ConnectionHandler is managing.
def active_connections?
- connection_pools.any?(&:active_connection?)
+ connection_pool_list.any?(&:active_connection?)
end
# Returns any connections in use by the current thread back to the pool,
# and also returns connections to the pool cached by threads that are no
# longer alive.
def clear_active_connections!
- connection_pools.each(&:release_connection)
+ connection_pool_list.each(&:release_connection)
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- connection_pools.each(&:clear_reloadable_connections!)
+ connection_pool_list.each(&:clear_reloadable_connections!)
end
def clear_all_connections!
- connection_pools.each(&:disconnect!)
+ connection_pool_list.each(&:disconnect!)
end
# Locate the connection of the nearest super class. This can be an
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 8790518d37..17dd71e898 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -422,7 +422,7 @@ module ActiveRecord
end
def index_name(table_name, options) #:nodoc:
- if Hash === options # legacy support
+ if Hash === options
if options[:column]
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
elsif options[:name]
@@ -617,15 +617,26 @@ module ActiveRecord
def add_index_options(table_name, column_name, options = {})
column_names = Array(column_name)
- index_name = index_name(table_name, :column => column_names)
+ index_name = index_name(table_name, column: column_names)
if Hash === options # legacy support, since this param was a string
+ options.assert_valid_keys(:unique, :order, :name, :where, :length)
+
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
else
+ if options
+ message = "Passing a string as third argument of `add_index` is deprecated and will" +
+ " be removed in Rails 4.1." +
+ " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
+
+ ActiveSupport::Deprecation.warn message
+ end
+
index_type = options
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index fdc3d2f6db..957027c1ee 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -3,9 +3,6 @@ require 'active_support/core_ext/object/duplicable'
require 'thread'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- end
-
module Core
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index c1c05eb8fb..26ad2087c1 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -883,7 +883,7 @@ module ActiveRecord
end
def enlist_fixture_connections
- ActiveRecord::Base.connection_handler.connection_pools.map(&:connection)
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
end
private
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 5499f37802..22347fcaef 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -32,7 +32,7 @@ module ActiveRecord
class PendingMigrationError < ActiveRecordError#:nodoc:
def initialize
- super("Migrations are pending run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve the issue")
+ super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve this issue.")
end
end
@@ -642,7 +642,11 @@ module ActiveRecord
def proper_table_name(name)
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
- name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ if name.respond_to? :table_name
+ name.table_name
+ else
+ "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ end
end
def migrations_paths
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 4c1c91e3df..711fc8b883 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -46,7 +46,11 @@ module ActiveRecord
{}
end
- def count
+ def count(*)
+ 0
+ end
+
+ def sum(*)
0
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 77e41ea927..5464ca6066 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -92,6 +92,33 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
+ begin
+ old_behavior, ActiveSupport::Deprecation.behavior = ActiveSupport::Deprecation.behavior, :stderr
+ whitelist_attributes = app.config.active_record.delete(:whitelist_attributes)
+
+ if respond_to?(:mass_assignment_sanitizer=)
+ mass_assignment_sanitizer = nil
+ else
+ mass_assignment_sanitizer = app.config.active_record.delete(:mass_assignment_sanitizer)
+ end
+
+ unless whitelist_attributes.nil? && mass_assignment_sanitizer.nil?
+ ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, []
+ Model based mass assignment security has been extracted
+ out of Rails into a gem. Please use the new recommended protection model for
+ params or add `protected_attributes` to your Gemfile to use the old one.
+
+ To disable this message remove the `whitelist_attributes` option from your
+ `config/application.rb` file and any `mass_assignment_sanitizer` options
+ from your `config/environments/*.rb` files.
+
+ See http://edgeguides.rubyonrails.org/security.html#mass-assignment for more information
+ EOF
+ end
+ ensure
+ ActiveSupport::Deprecation.behavior = old_behavior
+ end
+
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -122,8 +149,10 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
ActionDispatch::Reloader.send(hook) do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
+ if ActiveRecord::Base.connected?
+ ActiveRecord::Base.clear_reloadable_connections!
+ ActiveRecord::Base.clear_cache!
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 6120ffd4c3..c8edadf804 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -165,7 +165,9 @@ module ActiveRecord
if has_include?(column_names.first)
construct_relation_for_association_calculations.pluck(*column_names)
else
- result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
+ relation = spawn
+ relation.select_values = column_names
+ result = klass.connection.select_all(relation.arel, nil, bind_values)
columns = result.columns.map do |key|
klass.column_types.fetch(key) {
result.column_types.fetch(key) {
@@ -271,17 +273,19 @@ module ActiveRecord
group_fields = group_attrs
end
- group_aliases = group_fields.map { |field| column_alias_for(field) }
+ group_aliases = group_fields.map { |field|
+ column_alias_for(field)
+ }
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
[aliaz, column_for(field)]
}
- group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
+ group = group_fields
if operation == 'count' && column_name == :all
aggregate_alias = 'count_all'
else
- aggregate_alias = column_alias_for(operation, column_name)
+ aggregate_alias = column_alias_for([operation, column_name].join(' '))
end
select_values = [
@@ -300,7 +304,8 @@ module ActiveRecord
end
}
- relation = except(:group).group(group)
+ relation = except(:group)
+ relation.group_values = group
relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation, nil, bind_values)
@@ -329,10 +334,12 @@ module ActiveRecord
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
# column_alias_for("count(*)") # => "count_all"
# column_alias_for("count", "id") # => "count_id"
- def column_alias_for(*keys)
- keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
- table_name = keys.join(' ')
- table_name.downcase!
+ def column_alias_for(keys)
+ if keys.respond_to? :name
+ keys = "#{keys.relation.name}.#{keys.name}"
+ end
+
+ table_name = keys.to_s.downcase
table_name.gsub!(/\*/, 'all')
table_name.gsub!(/\W+/, ' ')
table_name.strip!
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index ab8b36c8ab..dbfa92bbbd 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,3 +1,4 @@
+require 'thread'
module ActiveRecord
module Delegation # :nodoc:
@@ -6,6 +7,8 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
+ @@delegation_mutex = Mutex.new
+
def self.delegate_to_scoped_klass(method)
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -32,13 +35,28 @@ module ActiveRecord
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
- ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ end
+ end
+
scoping { @klass.send(method, *args, &block) }
elsif Array.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ end
+ end
+
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :arel
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :arel
+ end
+ end
+
arel.send(method, *args, &block)
else
super
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index d8131680a3..eafe4a54c4 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/indifferent_access'
-
module ActiveRecord
module FinderMethods
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
@@ -225,7 +223,7 @@ module ActiveRecord
def construct_limited_ids_condition(relation)
orders = relation.order_values.map { |val| val.presence }.compact
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
+ values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
relation = relation.dup
@@ -234,8 +232,6 @@ module ActiveRecord
end
def find_with_ids(*ids)
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
-
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 81ad0cc768..b3712b4ad6 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -218,7 +218,6 @@ module ActiveRecord
# Like #order, but modifies relation in place.
def order!(*args)
args.flatten!
-
validate_order_args args
references = args.reject { |arg| Arel::Node === arg }
@@ -245,7 +244,6 @@ module ActiveRecord
# Like #reorder, but modifies relation in place.
def reorder!(*args)
args.flatten!
-
validate_order_args args
self.reordering_value = true
@@ -796,7 +794,7 @@ module ActiveRecord
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
- order_query.map do |o|
+ order_query.flat_map do |o|
case o
when Arel::Nodes::Ordering
o.reverse
@@ -814,7 +812,7 @@ module ActiveRecord
else
o
end
- end.flatten
+ end
end
def array_of_strings?(o)
@@ -825,7 +823,7 @@ module ActiveRecord
orders = order_values
orders = reverse_sql_order(orders) if reverse_order_value
- orders = orders.uniq.reject(&:blank?).map do |order|
+ orders = orders.uniq.reject(&:blank?).flat_map do |order|
case order
when Symbol
table[order].asc
@@ -834,7 +832,7 @@ module ActiveRecord
else
order
end
- end.flatten
+ end
arel.order(*orders) unless orders.empty?
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 920d6848c1..cf17b1d8a4 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,8 +1,5 @@
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- end
-
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 42f5b69d4e..1b1b479f1a 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -799,12 +799,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, developer.projects.count
end
- def test_counting_should_not_fire_sql_if_parent_is_unsaved
- assert_no_queries do
- assert_equal 0, Developer.new.projects.count
- end
- end
-
unless current_adapter?(:PostgreSQLAdapter)
def test_count_with_finder_sql
assert_equal 3, projects(:active_record).developers_with_finder_sql.count
@@ -862,4 +856,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def klass.name; 'Foo'; end
assert_deprecated { klass.has_and_belongs_to_many :posts, :delete_sql => 'lol' }
end
+
+ test "has and belongs to many associations on new records use null relations" do
+ projects = Developer.new.projects
+ assert_no_queries do
+ assert_equal [], projects
+ assert_equal [], projects.where(title: 'omg')
+ assert_equal [], projects.pluck(:title)
+ assert_equal 0, projects.count
+ end
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 50c23c863f..b1066fb24c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -262,12 +262,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal firm.limited_clients.length, firm.limited_clients.count
end
- def test_counting_should_not_fire_sql_if_parent_is_unsaved
- assert_no_queries do
- assert_equal 0, Person.new.readers.count
- end
- end
-
def test_finding
assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length
end
@@ -754,6 +748,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_custom_named_counter_cache
+ topic = topics(:first)
+
+ assert_difference "topic.reload.replies_count", -1 do
+ topic.approved_replies.clear
+ end
+ end
+
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -1648,4 +1650,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base)
assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' }
end
+
+ test "has many associations on new records use null relations" do
+ post = Post.new
+
+ assert_no_queries do
+ assert_equal [], post.comments
+ assert_equal [], post.comments.where(body: 'omg')
+ assert_equal [], post.comments.pluck(:body)
+ assert_equal 0, post.comments.sum(:id)
+ assert_equal 0, post.comments.count
+ 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 b2a5d9d6f7..8e52ce1d91 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -766,12 +766,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 1, authors(:mary).categories.general.count
end
- def test_counting_should_not_fire_sql_if_parent_is_unsaved
- assert_no_queries do
- assert_equal 0, Person.new.posts.count
- end
- end
-
def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes
post = posts(:eager_other)
@@ -876,4 +870,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar"
assert_equal [tags(:general)], post.reload.tags
end
+
+ test "has many through associations on new records use null relations" do
+ person = Person.new
+
+ assert_no_queries do
+ assert_equal [], person.posts
+ assert_equal [], person.posts.where(body: 'omg')
+ assert_equal [], person.posts.pluck(:body)
+ assert_equal 0, person.posts.sum(:tags_count)
+ assert_equal 0, person.posts.count
+ end
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 2d3cb654df..ea1cfa0805 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -206,6 +206,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
+ def test_build_association_dont_create_transaction
+ assert_no_queries {
+ Firm.new.build_account
+ }
+ end
+
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
scoped_count = pirate.association(:foo_bulb).scope.where_values.count
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 203e44857a..8644f2f496 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -616,6 +616,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 'value2', weird.read_attribute('a$b')
end
+ def test_group_weirds_by_from
+ Weird.create('a$b' => 'value', :from => 'aaron')
+ count = Weird.group(Weird.arel_table[:from]).count
+ assert_equal 1, count['aaron']
+ end
+
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
@@ -1442,6 +1448,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+ def test_cache_key_format_is_precise_enough
+ dev = Developer.first
+ key = dev.cache_key
+ dev.touch
+ assert_not_equal key, dev.cache_key
+ end
+
def test_uniq_delegates_to_scoped
scope = stub
Bird.stubs(:all).returns(mock(:uniq => scope))
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index abbf2a765e..65d28ea028 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -580,4 +580,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal ["Over There"], Possession.pluck(:where)
end
+
+ def test_pluck_replaces_select_clause
+ taks_relation = Topic.select(:approved, :id).order(:id)
+ assert_equal [1,2,3,4], taks_relation.pluck(:id)
+ assert_equal [false, true, true, true], taks_relation.pluck(:approved)
+ end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 631bf1aaac..2ddabe058f 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
@subklass = Class.new(@klass)
@handler = ConnectionHandler.new
- @handler.establish_connection @klass, Base.connection_pool.spec
+ @pool = @handler.establish_connection(@klass, Base.connection_pool.spec)
end
def test_retrieve_connection
@@ -44,6 +44,12 @@ module ActiveRecord
assert_same @handler.retrieve_connection_pool(@klass),
@handler.retrieve_connection_pool(@subklass)
end
+
+ def test_connection_pools
+ assert_deprecated do
+ assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools)
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index d44ac21b05..7db7953313 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -610,6 +610,11 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_by_heading("The First Topic!")
end
+ def test_find_by_one_attribute_bang_with_blank_defined
+ blank_topic = BlankTopic.create(title: "The Blank One")
+ assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
+ end
+
def test_find_by_one_attribute_with_conditions
assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index dd9492924c..0787414d8f 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -91,6 +91,22 @@ module ActiveRecord
assert connection.index_exists?(:testings, [:foo, :bar])
end
+ def test_valid_index_options
+ assert_raise ArgumentError do
+ connection.add_index :testings, :foo, unqiue: true
+ end
+ end
+
+ def test_deprecated_type_argument
+ message = "Passing a string as third argument of `add_index` is deprecated and will" +
+ " be removed in Rails 4.1." +
+ " Use add_index(:testings, [:foo, :bar], unique: true) instead"
+
+ assert_deprecated message do
+ connection.add_index :testings, [:foo, :bar], "UNIQUE"
+ end
+ end
+
def test_unique_index_exists
connection.add_index :testings, :foo, :unique => true
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 144302bd4a..d8a6565d54 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -64,7 +64,7 @@ module ActiveRecord
remove_reference table_name, :supplier
refute index_exists?(table_name, :supplier_id)
end
-
+
def test_does_not_delete_reference_type_column
with_polymorphic_column do
remove_reference table_name, :supplier
@@ -73,7 +73,7 @@ module ActiveRecord
assert column_exists?(table_name, :supplier_type, :string)
end
end
-
+
def test_deletes_reference_type_column
with_polymorphic_column do
remove_reference table_name, :supplier, polymorphic: true
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index fe9eddbdec..3f08f9ea4d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -185,6 +185,17 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal "James", mean_pirate.parrot.name
assert_equal "blue", mean_pirate.parrot.color
end
+
+ def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
+ Pirate.accepts_nested_attributes_for(:parrot)
+
+ mean_pirate_class = Class.new(Pirate) do
+ accepts_nested_attributes_for :parrot
+ end
+ mean_pirate = mean_pirate_class.new
+ mean_pirate.parrot_attributes = { :name => "James" }
+ assert_equal "James", mean_pirate.parrot.name
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@@ -464,17 +475,15 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
def test_should_unset_association_when_an_existing_record_is_destroyed
- @ship.reload
original_pirate_id = @ship.pirate.id
- @ship.attributes = {:pirate_attributes => {:id => @ship.pirate.id, :_destroy => true}}
- @ship.save!
+ @ship.update_attributes! pirate_attributes: { id: @ship.pirate.id, _destroy: true }
- assert_empty Pirate.where(["id = ?", original_pirate_id])
+ assert_empty Pirate.where(id: original_pirate_id)
assert_nil @ship.pirate_id
assert_nil @ship.pirate
@ship.reload
- assert_empty Pirate.where(["id = ?", original_pirate_id])
+ assert_empty Pirate.where(id: original_pirate_id)
assert_nil @ship.pirate_id
assert_nil @ship.pirate
end
@@ -491,7 +500,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' })
assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
-
+ ensure
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 4b27c16681..f7f4cebc5a 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -33,6 +33,7 @@ class Topic < ActiveRecord::Base
end
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'
has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
@@ -106,6 +107,12 @@ class ImportantTopic < Topic
serialize :important, Hash
end
+class BlankTopic < Topic
+ def blank?
+ true
+ end
+end
+
module Web
class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply'
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index eec06754a5..35778d008a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -770,6 +770,7 @@ ActiveRecord::Schema.define do
end
create_table :weirds, :force => true do |t|
t.string 'a$b'
+ t.string 'from'
end
except 'SQLite' do
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 6534c0af85..b55a706b2f 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,35 @@
## Rails 4.0.0 (unreleased) ##
+* `XmlMini.with_backend` now may be safely used with threads:
+
+ Thread.new do
+ XmlMini.with_backend("REXML") { rexml_power }
+ end
+ Thread.new do
+ XmlMini.with_backend("LibXML") { libxml_power }
+ end
+
+ Each thread will use it's own backend.
+
+ *Nikita Afanasenko*
+
+* Dependencies no longer trigger Kernel#autoload in remove_constant [fixes #8213]. *Xavier Noria*
+
+* Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead*
+
+* `#as_json` isolates options when encoding a hash.
+ Fix #8182
+
+ *Yves Senn*
+
+* Deprecate Hash#diff in favor of MiniTest's #diff. *Steve Klabnik*
+
+* Kernel#capture can catch output from subprocesses *Dmitry Vorotilin*
+
+* `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`.
+
+ *Nikita Afanasenko*
+
* Fixed timezone mapping of the Solomon Islands. *Steve Klabnik*
* Make callstack attribute optional in
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 30f4ded005..a4216d2cb4 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -8,19 +8,20 @@ Gem::Specification.new do |s|
s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.'
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('multi_json', '~> 1.3')
- s.add_dependency('tzinfo', '~> 0.3.33')
- s.add_dependency('minitest', '~> 4.1')
+ s.add_dependency 'i18n', '~> 0.6'
+ s.add_dependency 'multi_json', '~> 1.3'
+ s.add_dependency 'tzinfo', '~> 0.3.33'
+ s.add_dependency 'minitest', '~> 4.1'
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 6a0c4a015a..ff06436bd6 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -194,7 +194,7 @@ class Array
options = options.dup
options[:indent] ||= 2
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
options[:root] ||= \
if first.class != Hash && all? { |e| e.is_a?(first.class) }
underscored = ActiveSupport::Inflector.underscore(first.class.name)
@@ -208,12 +208,12 @@ class Array
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
children = options.delete(:children) || root.singularize
- attributes = options[:skip_types] ? {} : {:type => 'array'}
+ attributes = options[:skip_types] ? {} : { type: 'array' }
if empty?
builder.tag!(root, attributes)
else
- builder.__send__(:method_missing, root, attributes) do
+ builder.tag!(root, attributes) do
each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
yield builder if block_given?
end
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index f79b100b3b..640e6e9328 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -58,7 +58,7 @@ class Array
# size / number gives minor group size;
# size % number gives how many objects need extra accommodation;
# each group hold either division or division + 1 items.
- division = size / number
+ division = size.div number
modulo = size % number
# create a new array avoiding dup
@@ -67,9 +67,9 @@ class Array
number.times do |index|
length = division + (modulo > 0 && modulo > index ? 1 : 0)
- padding = fill_with != false &&
- modulo > 0 && length == division ? 1 : 0
- groups << slice(start, length).concat([fill_with] * padding)
+ groups << last_group = slice(start, length)
+ last_group << fill_with if fill_with != false &&
+ modulo > 0 && length == division
start += length
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 02ae57b4a6..439d380af7 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -8,6 +8,8 @@ require 'active_support/core_ext/date_and_time/calculations'
class Date
include DateAndTime::Calculations
+ @beginning_of_week_default = nil
+
class << self
attr_accessor :beginning_of_week_default
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index f5e3a9b842..85b0e10be2 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -74,14 +74,14 @@ class Hash
options = options.dup
options[:indent] ||= 2
options[:root] ||= 'hash'
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
builder = options[:builder]
builder.instruct! unless options.delete(:skip_instruct)
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
- builder.__send__(:method_missing, root) do
+ builder.tag!(root) do
each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
yield builder if block_given?
end
@@ -94,17 +94,17 @@ class Hash
private
def typecast_xml_value(value)
- case value.class.to_s
- when 'Hash'
+ case value
+ when Hash
if value['type'] == 'array'
_, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
if entries.nil? || (c = value['__content__'] && c.blank?)
[]
else
- case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
- when 'Array'
+ case entries # something weird with classes not matching here. maybe singleton methods breaking is_a?
+ when Array
entries.collect { |v| typecast_xml_value(v) }
- when 'Hash'
+ when Hash
[typecast_xml_value(entries)]
else
raise "can't typecast #{entries.inspect}"
@@ -135,10 +135,10 @@ class Hash
# how multipart uploaded files from HTML appear
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
- when 'Array'
+ when Array
value.map! { |i| typecast_xml_value(i) }
value.length > 1 ? value : value.first
- when 'String'
+ when String
value
else
raise "can't typecast #{value.class.name} - #{value.inspect}"
@@ -146,10 +146,10 @@ class Hash
end
def unrename_keys(params)
- case params.class.to_s
- when 'Hash'
+ case params
+ when Hash
Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
- when 'Array'
+ when Array
params.map { |v| unrename_keys(v) }
else
params
diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb
index 831dee8ecb..a27c55479a 100644
--- a/activesupport/lib/active_support/core_ext/hash/diff.rb
+++ b/activesupport/lib/active_support/core_ext/hash/diff.rb
@@ -6,6 +6,7 @@ class Hash
# {}.diff(1 => 2) # => {1 => 2}
# {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
def diff(other)
+ ActiveSupport::Deprecation.warn "Hash#diff is no longer used inside of Rails, and is being deprecated with no replacement. If you're using it to compare hashes for the purpose of testing, please use MiniTest's diff instead."
dup.
delete_if { |k, v| other[k] == v }.
merge!(other.dup.delete_if { |k, v| has_key?(k) })
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index bc97da6ef2..7b518821c8 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,5 @@
require 'rbconfig'
+require 'tempfile'
module Kernel
# Sets $VERBOSE to nil for the duration of the block and back to its original
@@ -66,19 +67,33 @@ module Kernel
# Captures the given stream and returns it:
#
- # stream = capture(:stdout) { puts 'Cool' }
- # stream # => "Cool\n"
+ # stream = capture(:stdout) { puts 'notice' }
+ # stream # => "notice\n"
+ #
+ # stream = capture(:stderr) { warn 'error' }
+ # stream # => "error\n"
+ #
+ # even for subprocesses:
+ #
+ # stream = capture(:stdout) { system('echo notice') }
+ # stream # => "notice\n"
+ #
+ # stream = capture(:stderr) { system('echo error 1>&2') }
+ # stream # => "error\n"
def capture(stream)
- begin
- stream = stream.to_s
- eval "$#{stream} = StringIO.new"
- yield
- result = eval("$#{stream}").string
- ensure
- eval("$#{stream} = #{stream.upcase}")
- end
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
- result
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
end
alias :silence :capture
diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb
new file mode 100644
index 0000000000..c2c30044f2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/struct.rb
@@ -0,0 +1,6 @@
+# Backport of Struct#to_h from Ruby 2.0
+class Struct # :nodoc:
+ def to_h
+ Hash[members.zip(values)]
+ end
+end unless Struct.instance_methods.include?(:to_h)
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 139d48f59c..796c5f9805 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,6 +1,8 @@
require 'active_support/time_with_zone'
class Time
+ @zone_default = nil
+
class << self
attr_accessor :zone_default
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 42746582fa..c75fb46263 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -572,7 +572,6 @@ module ActiveSupport #:nodoc:
# Determine if the given constant has been automatically loaded.
def autoloaded?(desc)
- # No name => anonymous module.
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
return false unless qualified_const_defined? name
@@ -641,19 +640,50 @@ module ActiveSupport #:nodoc:
end
def remove_constant(const) #:nodoc:
- return false unless qualified_const_defined? const
-
- # Normalize ::Foo, Foo, Object::Foo, and ::Object::Foo to Object::Foo
- names = const.to_s.sub(/^::(Object)?/, 'Object::').split("::")
- to_remove = names.pop
- parent = Inflector.constantize(names * '::')
-
- log "removing constant #{const}"
- constantized = constantize(const)
- constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
- parent.instance_eval { remove_const to_remove }
+ # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo.
+ normalized = const.to_s.sub(/\A::/, '')
+ normalized.sub!(/\A(Object::)+/, '')
+
+ constants = normalized.split('::')
+ to_remove = constants.pop
+ parent_name = constants.empty? ? 'Object' : constants.join('::')
+
+ if parent = safe_constantize(parent_name)
+ log "removing constant #{const}"
+
+ # In an autoloaded user.rb like this
+ #
+ # autoload :Foo, 'foo'
+ #
+ # class User < ActiveRecord::Base
+ # end
+ #
+ # we correctly register "Foo" as being autoloaded. But if the app does
+ # not use the "Foo" constant we need to be careful not to trigger
+ # loading "foo.rb" ourselves. While #const_defined? and #const_get? do
+ # require the file, #autoload? and #remove_const don't.
+ #
+ # We are going to remove the constant nonetheless ---which exists as
+ # far as Ruby is concerned--- because if the user removes the macro
+ # call from a class or module that were not autoloaded, as in the
+ # example above with Object, accessing to that constant must err.
+ unless parent.autoload?(to_remove)
+ begin
+ constantized = parent.const_get(to_remove, false)
+ rescue NameError
+ log "the constant #{const} is not reachable anymore, skipping"
+ return
+ else
+ constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
+ end
+ end
- true
+ begin
+ parent.instance_eval { remove_const to_remove }
+ rescue NameError
+ log "the constant #{const} is not reachable anymore, skipping"
+ end
+ end
end
protected
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 3910a2dc42..1eb2b4212b 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -286,10 +286,12 @@ module ActiveSupport
# ordinal(-11) # => "th"
# ordinal(-1021) # => "st"
def ordinal(number)
- if (11..13).include?(number.to_i.abs % 100)
+ abs_number = number.to_i.abs
+
+ if (11..13).include?(abs_number % 100)
"th"
else
- case number.to_i.abs % 10
+ case abs_number % 10
when 1; "st"
when 2; "nd"
when 3; "rd"
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index f65c831e04..7a5c351ca8 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -65,7 +65,7 @@ module ActiveSupport
# they can detect circular references.
options.merge(:encoder => self)
else
- options
+ options.dup
end
end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 04d170f801..6beb2b6afa 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,3 +1,4 @@
+require 'mutex_m'
require 'openssl'
module ActiveSupport
@@ -20,4 +21,57 @@ module ActiveSupport
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
end
end
+
+ # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
+ # re-executing the key generation process when it's called using the same salt and
+ # key_size
+ class CachingKeyGenerator
+ def initialize(key_generator)
+ @key_generator = key_generator
+ @cache_keys = {}.extend(Mutex_m)
+ end
+
+ # Returns a derived key suitable for use. The default key_size is chosen
+ # to be compatible with the default settings of ActiveSupport::MessageVerifier.
+ # i.e. OpenSSL::Digest::SHA1#block_length
+ def generate_key(salt, key_size=64)
+ @cache_keys.synchronize do
+ @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
+ end
+ end
+ end
+
+ class DummyKeyGenerator # :nodoc:
+ SECRET_MIN_LENGTH = 30 # Characters
+
+ def initialize(secret)
+ ensure_secret_secure(secret)
+ @secret = secret
+ end
+
+ def generate_key(salt)
+ @secret
+ end
+
+ private
+
+ # To prevent users from using something insecure like "Password" we make sure that the
+ # secret they've provided is at least 30 characters in length.
+ def ensure_secret_secure(secret)
+ if secret.blank?
+ raise ArgumentError, "A secret is required to generate an " +
+ "integrity hash for cookie session data. Use " +
+ "config.secret_key_base = \"some secret phrase of at " +
+ "least #{SECRET_MIN_LENGTH} characters\"" +
+ "in config/initializers/secret_token.rb"
+ end
+
+ if secret.length < SECRET_MIN_LENGTH
+ raise ArgumentError, "Secret should be something secure, " +
+ "like \"#{SecureRandom.hex(16)}\". The value you " +
+ "provided, \"#{secret}\", is shorter than the minimum length " +
+ "of #{SECRET_MIN_LENGTH} characters"
+ end
+ end
+ end
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 580267708c..1588674afc 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -39,10 +39,13 @@ module ActiveSupport
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
- def initialize(secret, options = {})
+ def initialize(secret, *signature_key_or_options)
+ options = signature_key_or_options.extract_options!
+ sign_secret = signature_key_or_options.first
@secret = secret
+ @sign_secret = sign_secret
@cipher = options[:cipher] || 'aes-256-cbc'
- @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer)
+ @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
@serializer = options[:serializer] || Marshal
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 8b06739b7f..c96ad17aba 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -5,16 +5,18 @@ require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
require 'active_support/testing/isolation'
-require 'active_support/testing/mocha_module'
require 'active_support/testing/constant_lookup'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/deprecation'
+begin
+ silence_warnings { require 'mocha/setup' }
+rescue LoadError
+end
+
module ActiveSupport
class TestCase < ::MiniTest::Spec
- include ActiveSupport::Testing::MochaModule
-
# Use AS::TestCase for the base class when describing a model
register_spec_type(self) do |desc|
Class === desc && desc < ActiveRecord::Base
diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb
deleted file mode 100644
index ed2942d23a..0000000000
--- a/activesupport/lib/active_support/testing/mocha_module.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module ActiveSupport
- module Testing
- module MochaModule
- begin
- require 'mocha_standalone'
- include Mocha::API
-
- def before_setup
- mocha_setup
- super
- end
-
- def after_teardown
- super
- mocha_verify
- mocha_teardown
- end
- rescue LoadError
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index 899467c45f..8ea2605733 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -1,10 +1,6 @@
-require 'active_support/concern'
-
module ActiveSupport
module Testing
module TaggedLogging
- extend ActiveSupport::Concern
-
attr_writer :tagged_logger
def before_setup
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 88f9acb588..d082a0a499 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -76,23 +76,24 @@ module ActiveSupport
)
end
- attr_reader :backend
delegate :parse, :to => :backend
+ def backend
+ current_thread_backend || @backend
+ end
+
def backend=(name)
- if name.is_a?(Module)
- @backend = name
- else
- require "active_support/xml_mini/#{name.downcase}"
- @backend = ActiveSupport.const_get("XmlMini_#{name}")
- end
+ backend = name && cast_backend_name_to_module(name)
+ self.current_thread_backend = backend if current_thread_backend
+ @backend = backend
end
def with_backend(name)
- old_backend, self.backend = backend, name
+ old_backend = current_thread_backend
+ self.current_thread_backend = name && cast_backend_name_to_module(name)
yield
ensure
- self.backend = old_backend
+ self.current_thread_backend = old_backend
end
def to_tag(key, value, options)
@@ -163,6 +164,25 @@ module ActiveSupport
f.content_type = entity['content_type']
f
end
+
+ private
+
+ def current_thread_backend
+ Thread.current[:xml_mini_backend]
+ end
+
+ def current_thread_backend=(name)
+ Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name)
+ end
+
+ def cast_backend_name_to_module(name)
+ if name.is_a?(Module)
+ name
+ else
+ require "active_support/xml_mini/#{name.downcase}"
+ ActiveSupport.const_get("XmlMini_#{name}")
+ end
+ end
end
XmlMini.backend = 'REXML'
diff --git a/activesupport/test/autoloading_fixtures/should_not_be_required.rb b/activesupport/test/autoloading_fixtures/should_not_be_required.rb
new file mode 100644
index 0000000000..1fcf170cc5
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/should_not_be_required.rb
@@ -0,0 +1 @@
+ShouldNotBeAutoloaded = 0
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index ec05213409..9b62295c96 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -24,52 +24,52 @@ end
module ConstantizeTestCases
def run_constantize_tests_on
- assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") }
- assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") }
- assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") }
- assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") }
- assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") }
- assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") }
- assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") }
- assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") }
- assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") }
- assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") }
- assert_nothing_raised { assert_equal Object, yield("") }
- assert_nothing_raised { assert_equal Object, yield("::") }
- assert_raise(NameError) { yield("UnknownClass") }
- assert_raise(NameError) { yield("UnknownClass::Ace") }
- assert_raise(NameError) { yield("UnknownClass::Ace::Base") }
- assert_raise(NameError) { yield("An invalid string") }
- assert_raise(NameError) { yield("InvalidClass\n") }
- assert_raise(NameError) { yield("Ace::ConstantizeTestCases") }
- assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") }
- assert_raise(NameError) { yield("Ace::Gas::Base") }
- assert_raise(NameError) { yield("Ace::Gas::ConstantizeTestCases") }
+ assert_equal Ace::Base::Case, yield("Ace::Base::Case")
+ assert_equal Ace::Base::Case, yield("::Ace::Base::Case")
+ assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice")
+ assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice")
+ assert_equal Ace::Gas::Case, yield("Ace::Gas::Case")
+ assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice")
+ assert_equal Case::Dice, yield("Case::Dice")
+ assert_equal Case::Dice, yield("Object::Case::Dice")
+ assert_equal ConstantizeTestCases, yield("ConstantizeTestCases")
+ assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases")
+ assert_equal Object, yield("")
+ assert_equal Object, yield("::")
+ assert_raises(NameError) { yield("UnknownClass") }
+ assert_raises(NameError) { yield("UnknownClass::Ace") }
+ assert_raises(NameError) { yield("UnknownClass::Ace::Base") }
+ assert_raises(NameError) { yield("An invalid string") }
+ assert_raises(NameError) { yield("InvalidClass\n") }
+ assert_raises(NameError) { yield("Ace::ConstantizeTestCases") }
+ assert_raises(NameError) { yield("Ace::Base::ConstantizeTestCases") }
+ assert_raises(NameError) { yield("Ace::Gas::Base") }
+ assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") }
end
def run_safe_constantize_tests_on
- assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") }
- assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") }
- assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") }
- assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") }
- assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") }
- assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") }
- assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") }
- assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") }
- assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") }
- assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") }
- assert_nothing_raised { assert_equal Object, yield("") }
- assert_nothing_raised { assert_equal Object, yield("::") }
- assert_nothing_raised { assert_equal nil, yield("UnknownClass") }
- assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace") }
- assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace::Base") }
- assert_nothing_raised { assert_equal nil, yield("An invalid string") }
- assert_nothing_raised { assert_equal nil, yield("InvalidClass\n") }
- assert_nothing_raised { assert_equal nil, yield("blargle") }
- assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") }
- assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") }
- assert_nothing_raised { assert_equal nil, yield("Ace::Gas::Base") }
- assert_nothing_raised { assert_equal nil, yield("Ace::Gas::ConstantizeTestCases") }
- assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") }
+ assert_equal Ace::Base::Case, yield("Ace::Base::Case")
+ assert_equal Ace::Base::Case, yield("::Ace::Base::Case")
+ assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice")
+ assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice")
+ assert_equal Ace::Gas::Case, yield("Ace::Gas::Case")
+ assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice")
+ assert_equal Case::Dice, yield("Case::Dice")
+ assert_equal Case::Dice, yield("Object::Case::Dice")
+ assert_equal ConstantizeTestCases, yield("ConstantizeTestCases")
+ assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases")
+ assert_equal Object, yield("")
+ assert_equal Object, yield("::")
+ assert_nil yield("UnknownClass")
+ assert_nil yield("UnknownClass::Ace")
+ assert_nil yield("UnknownClass::Ace::Base")
+ assert_nil yield("An invalid string")
+ assert_nil yield("InvalidClass\n")
+ assert_nil yield("blargle")
+ assert_nil yield("Ace::ConstantizeTestCases")
+ assert_nil yield("Ace::Base::ConstantizeTestCases")
+ assert_nil yield("Ace::Gas::Base")
+ assert_nil yield("Ace::Gas::ConstantizeTestCases")
+ assert_nil yield("#<Class:0x7b8b718b>::Nested_1")
end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 9dfa2cbf11..efa7582ab0 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -112,6 +112,14 @@ class ArrayExtToSTests < ActiveSupport::TestCase
end
class ArrayExtGroupingTests < ActiveSupport::TestCase
+ def setup
+ Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+ end
+
+ def teardown
+ Fixnum.send :public, :/
+ end
+
def test_in_groups_of_with_perfect_fit
groups = []
('a'..'i').to_a.in_groups_of(3) do |group|
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 6746b58cd3..c378dcd01d 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -656,7 +656,9 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_diff
- assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 }))
+ assert_deprecated do
+ assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 }))
+ end
end
def test_slice
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index 439bc87323..1583c1fa32 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -51,6 +51,8 @@ class KernelTest < ActiveSupport::TestCase
def test_capture
assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' }
assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' }
+ assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') }
+ assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') }
end
end
diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb
new file mode 100644
index 0000000000..0dff7b32d2
--- /dev/null
+++ b/activesupport/test/core_ext/struct_test.rb
@@ -0,0 +1,10 @@
+require 'abstract_unit'
+require 'active_support/core_ext/struct'
+
+class StructExt < ActiveSupport::TestCase
+ def test_to_h
+ x = Struct.new(:foo, :bar)
+ z = x.new(1, 2)
+ assert_equal({ foo: 1, bar: 2 }, z.to_h)
+ end
+end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 670a04e5df..67bd6669c5 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -928,6 +928,16 @@ class DependenciesTest < ActiveSupport::TestCase
assert ! defined?(DeleteMe)
end
+ def test_remove_constant_does_not_trigger_loading_autoloads
+ constant = 'ShouldNotBeAutoloaded'
+ Object.class_eval do
+ autoload constant, File.expand_path('../autoloading_fixtures/should_not_be_required', __FILE__)
+ end
+
+ assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant"
+ assert !defined?(ShouldNotBeAutoloaded)
+ end
+
def test_load_once_constants_should_not_be_unloaded
with_autoloading_fixtures do
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 7ed71f9abc..5bb2a45c87 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -22,6 +22,15 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ class CustomWithOptions
+ attr_accessor :foo, :bar
+
+ def as_json(options={})
+ options[:only] = %w(foo bar)
+ super(options)
+ end
+ end
+
TrueTests = [[ true, %(true) ]]
FalseTests = [[ false, %(false) ]]
NilTests = [[ nil, %(null) ]]
@@ -248,6 +257,15 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ def test_to_json_should_not_keep_options_around
+ f = CustomWithOptions.new
+ f.foo = "hello"
+ f.bar = "world"
+
+ hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
+ assert_equal(%({"foo":{"foo":"hello","bar":"world"},"other_hash":{"foo":"other_foo","test":"other_test"}}), hash.to_json)
+ end
+
def test_struct_encoding
Struct.new('UserNameAndEmail', :name, :email)
Struct.new('UserNameAndDate', :name, :date)
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index b544742300..d6c31396b6 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -56,7 +56,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
def test_alternative_serialization_method
- encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), :serializer => JSONSerializer.new)
+ encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new)
message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) })
assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
end
diff --git a/activesupport/test/spec_type_test.rb b/activesupport/test/spec_type_test.rb
index 95a982d8fd..9a6cb4ded2 100644
--- a/activesupport/test/spec_type_test.rb
+++ b/activesupport/test/spec_type_test.rb
@@ -4,7 +4,6 @@ require "active_record"
class SomeRandomModel < ActiveRecord::Base; end
class SpecTypeTest < ActiveSupport::TestCase
-
def assert_support actual
assert_equal ActiveSupport::TestCase, actual
end
@@ -13,7 +12,7 @@ class SpecTypeTest < ActiveSupport::TestCase
assert_equal MiniTest::Spec, actual
end
- def test_spec_type_resolves_for_actitive_record_constants
+ def test_spec_type_resolves_for_active_record_constants
assert_support MiniTest::Spec.spec_type(SomeRandomModel)
end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index c02bfa8497..64426d02e9 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -16,6 +16,9 @@ module ActiveSupport
def options
nil
end
+
+ def record(*args)
+ end
end
def test_standard_error_raised_within_setup_callback_is_puked
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index c56c032cde..19280ba74a 100644
--- a/activesupport/test/testing/constant_lookup_test.rb
+++ b/activesupport/test/testing/constant_lookup_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
class Foo; end
-class Bar < Foo;
+class Bar < Foo
def index; end
def self.index; end
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index 504fc96493..a025279e16 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -99,4 +99,66 @@ module XmlMiniTest
end
# TODO: test the remaining functions hidden in #to_tag.
end
+
+ class WithBackendTest < ActiveSupport::TestCase
+ module REXML end
+ module LibXML end
+ module Nokogiri end
+
+ setup do
+ @xml = ActiveSupport::XmlMini
+ end
+
+ test "#with_backend should switch backend and then switch back" do
+ @xml.backend = REXML
+ @xml.with_backend(LibXML) do
+ assert_equal LibXML, @xml.backend
+ @xml.with_backend(Nokogiri) do
+ assert_equal Nokogiri, @xml.backend
+ end
+ assert_equal LibXML, @xml.backend
+ end
+ assert_equal REXML, @xml.backend
+ end
+
+ test "backend switch inside #with_backend block" do
+ @xml.with_backend(LibXML) do
+ @xml.backend = REXML
+ assert_equal REXML, @xml.backend
+ end
+ assert_equal REXML, @xml.backend
+ end
+ end
+
+ class ThreadSafetyTest < ActiveSupport::TestCase
+ module REXML end
+ module LibXML end
+
+ setup do
+ @xml = ActiveSupport::XmlMini
+ end
+
+ test "#with_backend should be thread-safe" do
+ @xml.backend = REXML
+ t = Thread.new do
+ @xml.with_backend(LibXML) { sleep 1 }
+ end
+ sleep 0.1 while t.status != "sleep"
+
+ # We should get `old_backend` here even while another
+ # thread is using `new_backend`.
+ assert_equal REXML, @xml.backend
+ end
+
+ test "nested #with_backend should be thread-safe" do
+ @xml.with_backend(REXML) do
+ t = Thread.new do
+ @xml.with_backend(LibXML) { sleep 1 }
+ end
+ sleep 0.1 while t.status != "sleep"
+
+ assert_equal REXML, @xml.backend
+ end
+ end
+ end
end
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 9f5e101d1c..589c96e0e9 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -26,11 +26,13 @@ dl { margin: 0 0 1.5em 0; }
dl dt { font-weight: bold; }
dd { margin-left: 1.5em;}
-pre,code { margin: 1.5em 0; overflow: auto; color: #222;}
-pre,code {
- font-size: 1em;
- font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
- line-height: 1.5;
+pre, code {
+ font-size: 1em;
+ font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+ line-height: 1.5;
+ margin: 1.5em 0;
+ overflow: auto;
+ color: #222;
}
pre,tt,code,.note>p {
white-space: pre-wrap; /* css-3 */
@@ -92,14 +94,14 @@ body {
line-height: 1.5em;
background: #fff;
color: #999;
- }
+}
.wrapper {
text-align: left;
margin: 0 auto;
max-width: 960px;
padding: 0 1em;
- }
+}
.red-button {
display: inline-block;
@@ -164,7 +166,6 @@ body {
.more-info:last-child:after {
content: "";
}
-
}
@media screen and (max-width: 1024px) {
@@ -218,7 +219,7 @@ body {
color: #FFF;
padding: 1.5em 0;
z-index: 99;
- }
+}
#feature {
background: #d5e9f6 url(../images/feature_tile.gif) repeat-x;
@@ -229,12 +230,12 @@ body {
#container {
color: #333;
padding: 0.5em 0 1.5em 0;
- }
+}
#mainCol {
max-width: 630px;
margin-left: 2em;
- }
+}
#subCol {
position: absolute;
@@ -247,7 +248,7 @@ body {
font-size: 0.9285em;
line-height: 1.3846em;
margin-right: 1em;
- }
+}
@media screen and (max-width: 800px) {
@@ -265,7 +266,7 @@ body {
#footer {
padding: 2em 0;
background: #222 url(../images/footer_tile.gif) repeat-x;
- }
+}
#footer .wrapper {
padding-left: 1em;
max-width: 960px;
@@ -284,12 +285,11 @@ body {
a, a:link, a:visited {
color: #ee3f3f;
text-decoration: underline;
- }
+}
#mainCol a, #subCol a, #feature a {color: #980905;}
#mainCol a code, #subCol a code, #feature a code {color: #980905;}
-
/* Navigation
--------------------------------------- */
@@ -313,7 +313,6 @@ a, a:link, a:visited {
background: #980905;
position: relative;
color: white;
- cursor: pointer;
}
.guides-index .guides-index-item {
@@ -345,7 +344,6 @@ a, a:link, a:visited {
}
@media screen and (max-width: 480px) {
-
.nav {
float: none;
width: 100%;
@@ -408,7 +406,8 @@ a, a:link, a:visited {
padding: 0;
}
#guides dt {padding:0; margin: 0.5em 0 0;}
-#guides a {color: #FFF; background: none !important;}
+#guides a {color: #FFF; background: none !important; text-decoration: none;}
+#guides a:hover {text-decoration: underline;}
#guides .L, #guides .R {float: left; width: 50%; margin: 0; padding: 0;}
#guides .R {float: right;}
#guides hr {
@@ -427,14 +426,14 @@ h1 {
line-height: 1em;
margin: 0.6em 0 .2em;
font-weight: bold;
- }
+}
h2 {
font-size: 2.1428em;
line-height: 1em;
margin: 0.7em 0 .2333em;
font-weight: bold;
- }
+}
@media screen and (max-width: 480px) {
h2 {
@@ -447,7 +446,7 @@ h3 {
line-height: 1.286em;
margin: 0.875em 0 0.2916em;
font-weight: bold;
- }
+}
@media screen and (max-width: 480px) {
h3 {
@@ -460,7 +459,7 @@ h4 {
line-height: 1.2em;
margin: 1.6667em 0 .3887em;
font-weight: bold;
- }
+}
h5 {
font-size: 1em;
@@ -474,7 +473,7 @@ h6 {
line-height: 1.5em;
margin: 1em 0 .5em;
font-weight: normal;
- }
+}
.section {
padding-bottom: 0.25em;
@@ -542,13 +541,19 @@ h6 {
}
#mainCol dt, #subCol dt {
- font-size: 1em;
+ font-size: 1.2857em;
padding: 0.125em 0 0.25em 0;
margin-bottom: 0;
/*background: url(../images/book_icon.gif) no-repeat left top;
padding: 0.125em 0 0.25em 28px;*/
}
+@media screen and (max-width: 480px) {
+ #mainCol dt, #subCol dt {
+ font-size: 1em;
+ }
+}
+
#mainCol dd.work-in-progress, #subCol dd.work-in-progress {
background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
border: none;
@@ -609,10 +614,10 @@ div.code_container {
}
#mainCol div.todo {
- background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
- border: none;
- padding: 1em 1em 0.25em 48px;
- margin: 0.25em 0 1.5em 0;
+ background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
+ border: none;
+ padding: 1em 1em 0.25em 48px;
+ margin: 0.25em 0 1.5em 0;
}
.note code, .info code, .todo code {border:none; background: none; padding: 0;}
@@ -640,11 +645,11 @@ div.code_container {
--------------------------------------- */
.clearfix:after {
- content: ".";
- display: block;
- height: 0;
- clear: both;
- visibility: hidden;
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
}
.clearfix {display: inline-block;}
@@ -655,13 +660,13 @@ div.code_container {
/* Same bottom margin for special boxes than for regular paragraphs, this way
intermediate whitespace looks uniform. */
div.code_container, div.important, div.caution, div.warning, div.note, div.info {
- margin-bottom: 1.5em;
+ margin-bottom: 1.5em;
}
/* Remove bottom margin of paragraphs in special boxes, otherwise they get a
spurious blank area below with the box background. */
div.important p, div.caution p, div.warning p, div.note p, div.info p {
- margin-bottom: 1em;
+ margin-bottom: 1em;
}
/* Edge Badge
@@ -683,19 +688,18 @@ table td, table th { padding: 9px 10px; text-align: left; }
/* Mobile */
@media only screen and (max-width: 767px) {
-
- table.responsive { margin-bottom: 0; }
-
- .pinned { position: absolute; left: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-right: 1px solid #ccc; border-left: 1px solid #ccc; }
- .pinned table { border-right: none; border-left: none; width: 100%; }
- .pinned table th, .pinned table td { white-space: nowrap; }
- .pinned td:last-child { border-bottom: 0; }
-
- div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-right: 1px solid #ccc; }
- div.table-wrapper div.scrollable table { margin-left: 35%; }
- div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; }
-
- table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; }
- table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; }
-
+ table.responsive { margin-bottom: 0; }
+
+ .pinned { position: absolute; left: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-right: 1px solid #ccc; border-left: 1px solid #ccc; }
+ .pinned table { border-right: none; border-left: none; width: 100%; }
+ .pinned table th, .pinned table td { white-space: nowrap; }
+ .pinned td:last-child { border-bottom: 0; }
+
+ div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-right: 1px solid #ccc; }
+ div.table-wrapper div.scrollable table { margin-left: 35%; }
+ div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; }
+
+ table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; }
+ table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; }
+
}
diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb
index f36ebdda18..969ecaad65 100644
--- a/guides/code/getting_started/config/initializers/secret_token.rb
+++ b/guides/code/getting_started/config/initializers/secret_token.rb
@@ -6,4 +6,4 @@
# no regular words or you'll be exposed to dictionary attacks.
# Make sure your secret key is kept private
# if you're sharing your code publicly.
-Blog::Application.config.secret_token = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
+Blog::Application.config.secret_key_base = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 2f36af1fb3..c3fe5b8799 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -9,7 +9,7 @@ module RailsGuides
<<-HTML
<div class="code_container">
<pre class="brush: #{brush_for(language)}; gutter: false; toolbar: false">
-#{ERB::Util.h(code).strip}
+#{ERB::Util.h(code)}
</pre>
</div>
HTML
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index f24a981c6e..ecb8dd04f5 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -74,9 +74,48 @@ Documentation
* Guides are rewritten in GitHub Flavored Markdown.
+* Guides have a responsive design.
+
Railties
--------
+* Ensure that RAILS_ENV is set when accessing Rails.env.
+
+* Don't eager-load app/assets and app/views.
+
+* Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`.
+
+* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well.
+
+* Set a different cache per environment for assets pipeline through `config.assets.cache`.
+
+* `Rails.public_path` now returns a Pathname object.
+
+* Remove highly uncommon `config.assets.manifest` option for moving the manifest path. This option is now unsupported in sprockets-rails.
+
+* Add `config.action_controller.permit_all_parameters` to disable StrongParameters protection, it's false by default.
+
+* Remove `config.active_record.whitelist_attributes` and `config.active_record.mass_assignment_sanitizer` from new applications since MassAssignmentSecurity has been extracted from Rails.
+
+* Change `rails new` and `rails plugin new` generators to name the `.gitkeep` files as `.keep` in a more SCM-agnostic way. Change `--skip-git` option to only skip the `.gitignore` file and still generate the `.keep` files. Add `--skip-keeps` option to skip the `.keep` files.
+
+* Fixed support for DATABASE_URL environment variable for rake db tasks.
+
+* rails dbconsole now can use SSL for MySQL. The database.yml options sslca, sslcert, sslcapath, sslcipher and sslkey now affect rails dbconsole.
+
+* Correctly handle SCRIPT_NAME when generating routes to engine in application that's mounted at a sub-uri. With this behavior, you *should not* use default_url_options[:script_name] to set proper application's mount point by yourself.
+
+* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded.
+
+* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
+
+ rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
+
+ will generate the migration with:
+
+ add_reference :products, :user, index: true
+ add_reference :products, :supplier, polymorphic: true, index: true
+
* Allow scaffold/model/migration generators to accept a `polymorphic` modifier for `references`/`belongs_to`, for instance
```
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 7de95a33fd..6f161e83ea 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -219,7 +219,7 @@ Rails sets up (for the CookieStore) a secret key used for signing the session da
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...'
+YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'
```
NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 6e7c71c9cf..ac763d6e0e 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -113,7 +113,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
-* `config.secret_token` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_token` initialized to a random key in `config/initializers/secret_token.rb`.
+* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`.
* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 23fa086385..feb32eb06f 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -376,6 +376,44 @@ Now you need to get other people to look at your patch, just as you've looked at
It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem.
+#### Squashing commits
+
+One of the things that we may ask you to do is "squash your commits," which
+will combine all of your commits into a single commit. We prefer pull requests
+that are a single commit. This makes it easier to backport changes to stable
+branches, squashing makes it easier to revert bad commits, and the git history
+can be a bit easier to follow. Rails is a large project, and a bunch of
+extraneous commits can add a lot of noise.
+
+In order to do this, you'll need to have a git remote that points at the main
+Rails repository. This is useful anyway, but just in case you don't have it set
+up, make sure that you do this first:
+
+```bash
+$ git remote add upstream https://github.com/rails/rails.git
+```
+
+You can call this remote whatever you'd like, but if you don't use `upstream`,
+then change the name to your own in the instructions below.
+
+Given that your remote branch is called `my_pull_request`, then you can do the
+following:
+
+```bash
+$ git fetch upstream
+$ git checkout my_pull_request
+$ git rebase upstream/master
+$ git rebase -i
+
+< Choose 'squash' for all of your commits except the first one. >
+< Edit the commit message to make sense, and describe all your changes. >
+
+$ git push origin my_pull_request -f
+```
+
+You should be able to refresh the pull request on GitHub and see that it has
+been updated.
+
### Backporting
Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 011303c311..10b9dddd02 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -322,7 +322,7 @@ this:
end
```
-Notice the format.js in the respond_to block; that allows the controller to
+Notice the format.js in the `respond_to` block; that allows the controller to
respond to your Ajax request. You then have a corresponding
`app/views/users/create.js.erb` view file that generates the actual JavaScript
code that will be sent and executed on the client side.
@@ -379,7 +379,7 @@ $(document).on "page:change", ->
For more details, including other events you can bind to, check out [the
Turbolinks
-README](https://github.com/rails/turbolinks/blob/ec9ca4d6cf9626e03a672f3b9e7968c816aff94e/README.md).
+README](https://github.com/rails/turbolinks/blob/master/README.md).
Other Resources
---------------
diff --git a/rails.gemspec b/rails.gemspec
index 97f6dfeac8..ba94354bf1 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../RAILS_VERSION",__FILE__)).strip
+version = File.read(File.expand_path('../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -8,8 +8,9 @@ Gem::Specification.new do |s|
s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.'
s.required_ruby_version = '>= 1.9.3'
- s.required_rubygems_version = ">= 1.8.11"
- s.license = 'MIT'
+ s.required_rubygems_version = '>= 1.8.11'
+
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
@@ -19,11 +20,12 @@ Gem::Specification.new do |s|
s.executables = []
s.files = Dir['guides/**/*']
- s.add_dependency('activesupport', version)
- s.add_dependency('actionpack', version)
- s.add_dependency('activerecord', version)
- s.add_dependency('actionmailer', version)
- s.add_dependency('railties', version)
- s.add_dependency('bundler', '~> 1.2')
- s.add_dependency('sprockets-rails', '~> 2.0.0.rc1')
+ s.add_dependency 'activesupport', version
+ s.add_dependency 'actionpack', version
+ s.add_dependency 'activerecord', version
+ s.add_dependency 'actionmailer', version
+ s.add_dependency 'railties', version
+
+ s.add_dependency 'bundler', '>= 1.2.2', '< 2.0'
+ s.add_dependency 'sprockets-rails', '~> 2.0.0.rc1'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index cc77d08684..a0545916ca 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,12 @@
## Rails 4.0.0 (unreleased) ##
+* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres*
+
+* Add dummy app Rake tasks when --skip-test-unit and --dummy-path is passed to the plugin generator.
+ Fix #8121
+
+ *Yves Senn*
+
* Ensure that RAILS_ENV is set when accessing Rails.env *Steve Klabnik*
* Don't eager-load app/assets and app/views *Elia Schito*
@@ -9,7 +16,7 @@
* New test locations `test/models`, `test/helpers`, `test/controllers`, and
`test/mailers`. Corresponding rake tasks added as well. *Mike Moore*
-* Set a different cache per environment for assets pipeline
+* Set a different cache per environment for assets pipeline
through `config.assets.cache`.
*Guillermo Iguaran*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 9ef001c7d0..ae3993fbd8 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,5 +1,7 @@
require 'fileutils'
require 'active_support/queueing'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
require 'rails/engine'
module Rails
@@ -106,32 +108,59 @@ module Rails
def key_generator
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
- @key_generator ||= ActiveSupport::KeyGenerator.new(config.secret_token, iterations: 1000)
+ @caching_key_generator ||= begin
+ if config.secret_key_base
+ key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
+ ActiveSupport::CachingKeyGenerator.new(key_generator)
+ else
+ ActiveSupport::DummyKeyGenerator.new(config.secret_token)
+ end
+ end
end
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
# Currently stores:
#
- # * "action_dispatch.parameter_filter" => config.filter_parameters,
- # * "action_dispatch.secret_token" => config.secret_token,
- # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
- # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
- # * "action_dispatch.logger" => Rails.logger,
- # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
+ # * "action_dispatch.parameter_filter" => config.filter_parameters
+ # * "action_dispatch.secret_token" => config.secret_token,
+ # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
+ # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local
+ # * "action_dispatch.logger" => Rails.logger
+ # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
+ # * "action_dispatch.key_generator" => key_generator
+ # * "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt
+ # * "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
#
# These parameters will be used by middlewares and engines to configure themselves
#
def env_config
- @env_config ||= super.merge({
- "action_dispatch.parameter_filter" => config.filter_parameters,
- "action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
- "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
- "action_dispatch.logger" => Rails.logger,
- "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
- "action_dispatch.key_generator" => key_generator
- })
+ @env_config ||= begin
+ if config.secret_key_base.nil?
+ ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " +
+ "This should be used instead of the old deprecated config.secret_token. " +
+ "Set config.secret_key_base instead of config.secret_token in config/initializers/secret_token.rb"
+ if config.secret_token.blank?
+ raise "You must set config.secret_key_base in your app's config"
+ end
+ end
+
+ super.merge({
+ "action_dispatch.parameter_filter" => config.filter_parameters,
+ "action_dispatch.secret_token" => config.secret_token,
+ "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
+ "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
+ "action_dispatch.logger" => Rails.logger,
+ "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
+ "action_dispatch.key_generator" => key_generator,
+ "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt,
+ "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
+ })
+ end
end
## Rails internal API
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index cc21213f1c..f97e66985c 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -10,7 +10,7 @@ module Rails
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
- :railties_order, :relative_url_root, :secret_token,
+ :railties_order, :relative_url_root, :secret_key_base, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:queue, :queue_consumer, :beginning_of_week
@@ -46,6 +46,8 @@ module Rails
@queue = ActiveSupport::SynchronousQueue.new
@queue_consumer = nil
@eager_load = nil
+ @secret_token = nil
+ @secret_key_base = nil
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index c84fa832f5..90359d1c08 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -154,7 +154,7 @@ module Rails
full_path_command = nil
found = commands.detect do |cmd|
- dir = dirs_on_path.detect do |path|
+ dirs_on_path.detect do |path|
full_path_command = File.join(path, cmd)
File.executable? full_path_command
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 80fdc06cd2..0b897d736d 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -72,6 +72,7 @@ module Rails
console = ActiveSupport::Logger.new($stdout)
console.formatter = Rails.logger.formatter
+ console.level = Rails.logger.level
Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 2758a91d1b..593d2acfc7 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -66,9 +66,6 @@
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
- # Enable threaded mode.
- # config.threadsafe!
-
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index 3c5611ca59..e5caab3672 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -7,6 +7,6 @@
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
-# Make sure your secret_token is kept private
+# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
-<%= app_const %>.config.secret_token = '<%= app_secret %>'
+<%= app_const %>.config.secret_key_base = '<%= app_secret %>'
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 4a099a4ce2..df07de9922 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.
-<%= app_const %>.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
+<%= app_const %>.config.session_store :encrypted_cookie_store, key: <%= "'_#{app_name}_session'" %>
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 4a0bcc35a4..48ce3e86a1 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -225,7 +225,7 @@ task default: :test
end
def create_test_dummy_files
- return if options[:skip_test_unit] && options[:dummy_path] == 'test/dummy'
+ return unless with_dummy_app?
create_dummy_app
end
@@ -279,6 +279,10 @@ task default: :test
options[:mountable]
end
+ def with_dummy_app?
+ options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy'
+ end
+
def self.banner
"rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]"
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 1369140537..65a5bae712 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
-<% if full? && !options[:skip_active_record] && !options[:skip_test_unit] -%>
+<% if full? && !options[:skip_active_record] && with_dummy_app? -%>
APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
<% end %>
diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
index 121205b254..a0e5553e44 100644
--- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
+++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
@@ -32,7 +32,7 @@ module Rails
# route prepends two spaces onto the front of the string that is passed, this corrects that
route route_string[2..-1]
end
-
+
private
def route_string
@route_string ||= ""
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 3f59bb8733..7be2333981 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -24,8 +24,8 @@ module Rails
def call_app(request, env)
# Put some space between requests in development logs.
if Rails.env.development?
- Rails.logger.info ''
- Rails.logger.info ''
+ Rails.logger.debug ''
+ Rails.logger.debug ''
end
Rails.logger.info started_request_message(request)
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index cd59fbe599..9ad3a4e6d6 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -88,7 +88,7 @@ namespace :test do
def t.file_list
if File.directory?(".svn")
changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] }
- elsif File.directory?(".git")
+ elsif system "git rev-parse --git-dir 2>&1 >/dev/null"
changed_since_checkin = silence_stderr { `git ls-files --modified --others --exclude-standard` }.split.map { |path| path.chomp }
else
abort "Not a Subversion or Git checkout."
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 6d28947e83..e39430560f 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -6,24 +6,27 @@ Gem::Specification.new do |s|
s.version = version
s.summary = 'Tools for creating, working with, and running Rails applications.'
s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.'
+
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
- s.require_path = 'lib'
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
+ s.require_path = 'lib'
- s.bindir = 'bin'
- s.executables = ['rails']
+ s.bindir = 'bin'
+ s.executables = ['rails']
s.rdoc_options << '--exclude' << '.'
- s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '>= 0.15.4', '< 2.0')
- s.add_dependency('rdoc', '~> 3.4')
- s.add_dependency('activesupport', version)
- s.add_dependency('actionpack', version)
+ s.add_dependency 'activesupport', version
+ s.add_dependency 'actionpack', version
+
+ s.add_dependency 'rake', '>= 0.8.7'
+ s.add_dependency 'thor', '>= 0.15.4', '< 2.0'
+ s.add_dependency 'rdoc', '~> 3.4'
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index dfcf5aa27d..2ea1d2aff4 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -14,5 +14,6 @@ require 'rails/all'
module TestApp
class Application < Rails::Application
config.root = File.dirname(__FILE__)
+ config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index f86877db34..f98915d1cc 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -83,6 +83,32 @@ module ApplicationTests
end
end
+ def test_precompile_does_not_hit_the_database
+ app_file "app/assets/javascripts/application.js", "alert();"
+ app_file "app/assets/javascripts/foo/application.js", "alert();"
+ app_file "app/controllers/user_controller.rb", <<-eoruby
+ class UserController < ApplicationController; end
+ eoruby
+ app_file "app/models/user.rb", <<-eoruby
+ class User < ActiveRecord::Base; end
+ eoruby
+
+ ENV['RAILS_ENV'] = 'production'
+ ENV['DATABASE_URL'] = 'postgresql://baduser:badpass@127.0.0.1/dbname'
+
+ precompile!
+
+ files = Dir["#{app_path}/public/assets/application-*.js"]
+ files << Dir["#{app_path}/public/assets/foo/application-*.js"].first
+ files.each do |file|
+ assert_not_nil file, "Expected application.js asset to be generated, but none found"
+ assert_equal "alert();".strip, File.read(file).strip
+ end
+ ensure
+ ENV.delete 'RAILS_ENV'
+ ENV.delete 'DATABASE_URL'
+ end
+
test "precompile application.js and application.css and all other non JS/CSS files" do
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/stylesheets/application.css", "body{}"
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index c4c1100f19..b9d18f4582 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -225,21 +225,24 @@ module ApplicationTests
assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
- test "config.secret_token is sent in env" do
+ test "Use key_generator when secret_key_base is set" do
make_basic_app do |app|
- app.config.secret_token = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
app.config.session_store :disabled
end
class ::OmgController < ActionController::Base
def index
cookies.signed[:some_key] = "some_value"
- render text: env["action_dispatch.secret_token"]
+ render text: cookies[:some_key]
end
end
get "/"
- assert_equal 'b3c631c314c0bbca50c1b2843150fe33', last_response.body
+
+ secret = app.key_generator.generate_key('signed cookie')
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 'some_value', verifier.verify(last_response.body)
end
test "protect from forgery is the default in a new app" do
@@ -568,7 +571,6 @@ module ApplicationTests
assert_respond_to app, :env_config
assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters
- assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token
assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
assert_equal app.env_config['action_dispatch.logger'], Rails.logger
assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 528bec58ef..40d1655c9b 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -219,7 +219,7 @@ module ApplicationTests
orig_rails_env, Rails.env = Rails.env, 'development'
ActiveRecord::Base.establish_connection
assert ActiveRecord::Base.connection
- assert_match /#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]
+ assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database])
ensure
ActiveRecord::Base.remove_connection
ENV["DATABASE_URL"] = orig_database_url if orig_database_url
@@ -236,7 +236,7 @@ module ApplicationTests
ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}"
ActiveRecord::Base.establish_connection
assert ActiveRecord::Base.connection
- assert_match /#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]
+ assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database])
ensure
ActiveRecord::Base.remove_connection
ENV["DATABASE_URL"] = orig_database_url if orig_database_url
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index 9d97bae9ae..fde13eeb94 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -1,4 +1,6 @@
require 'isolation/abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
module ApplicationTests
class RemoteIpTest < ActiveSupport::TestCase
@@ -8,7 +10,7 @@ module ApplicationTests
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
'action_dispatch.show_exceptions' => false,
- 'action_dispatch.secret_token' => 'b3c631c314c0bbca50c1b2843150fe33'
+ 'action_dispatch.key_generator' => ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
)
endpoint = Proc.new do |e|
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 5ce41caf61..a5fdfbf887 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -128,5 +128,164 @@ module ApplicationTests
get '/foo/read_cookie' # Cookie shouldn't be changed
assert_equal '"1"', last_response.body
end
+
+ test "session using encrypted cookie store" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_session
+ session[:foo] = 1
+ render nothing: true
+ end
+
+ def read_session
+ render text: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render text: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render text: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.session_store :encrypted_cookie_store, key: '_myapp_session'
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/foo/write_session'
+ get '/foo/read_session'
+ assert_equal '1', last_response.body
+
+ get '/foo/read_encrypted_cookie'
+ assert_equal '1', last_response.body
+
+ secret = app.key_generator.generate_key('encrypted cookie')
+ sign_secret = app.key_generator.generate_key('signed encrypted cookie')
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+
+ get '/foo/read_raw_cookie'
+ assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
+ end
+
+ test "session using upgrade signature to encryption cookie store works the same way as encrypted cookie store" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_session
+ session[:foo] = 1
+ render nothing: true
+ end
+
+ def read_session
+ render text: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render text: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render text: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/foo/write_session'
+ get '/foo/read_session'
+ assert_equal '1', last_response.body
+
+ get '/foo/read_encrypted_cookie'
+ assert_equal '1', last_response.body
+
+ secret = app.key_generator.generate_key('encrypted cookie')
+ sign_secret = app.key_generator.generate_key('signed encrypted cookie')
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+
+ get '/foo/read_raw_cookie'
+ assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
+ end
+
+ test "session using upgrade signature to encryption cookie store upgrades session to encrypted mode" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_raw_session
+ # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
+ cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
+ render nothing: true
+ end
+
+ def write_session
+ session[:foo] = session[:foo] + 1
+ render nothing: true
+ end
+
+ def read_session
+ render text: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render text: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render text: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/foo/write_raw_session'
+ get '/foo/read_session'
+ assert_equal '1', last_response.body
+
+ get '/foo/write_session'
+ get '/foo/read_session'
+ assert_equal '2', last_response.body
+
+ get '/foo/read_encrypted_cookie'
+ assert_equal '2', last_response.body
+
+ secret = app.key_generator.generate_key('encrypted cookie')
+ sign_secret = app.key_generator.generate_key('signed encrypted cookie')
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+
+ get '/foo/read_raw_cookie'
+ assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo']
+ end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index c5a68a5152..c6aea03d8c 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -106,6 +106,35 @@ module ApplicationTests
end
end
+ def test_rake_test_uncommitted_always_find_git_in_parent_dir
+ app_name = File.basename(app_path)
+ app_dir = File.dirname(app_path)
+ moved_app_name = app_name + '_moved'
+ moved_app_path = "#{app_path}/#{moved_app_name}"
+
+ Dir.chdir(app_dir) do
+ # Go from "./app/" to "./app/app_moved"
+ FileUtils.mv(app_name, moved_app_name)
+ FileUtils.mkdir(app_name)
+ FileUtils.mv(moved_app_name, app_name)
+ # Initialize the git repository and start the test.
+ Dir.chdir(app_name) do
+ `git init`
+ Dir.chdir(moved_app_name){ `rake db:migrate` }
+ silence_stderr { Dir.chdir(moved_app_name) { `rake test:uncommitted` } }
+ assert_equal 0, $?.exitstatus
+ end
+ end
+ end
+
+ def test_rake_test_uncommitted_fails_with_no_scm
+ Dir.chdir(app_path){ `rake db:migrate` }
+ Dir.chdir(app_path) do
+ silence_stderr { `rake test:uncommitted` }
+ assert_equal 1, $?.exitstatus
+ end
+ end
+
def test_rake_routes_calls_the_route_inspector
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index 2a48adae5c..0905757442 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -14,7 +14,7 @@ module ApplicationTests
require "action_controller/railtie"
class MyApp < Rails::Application
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.eager_load = false
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index e047d4882d..f99ea13022 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -110,7 +110,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
def start(argv = [])
rails_console = Rails::Console.new(app, parse_arguments(argv))
- @output = output = capture(:stdout) { rails_console.start }
+ @output = capture(:stdout) { rails_console.start }
end
def app
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 62e1f3531e..5ea31f2e0f 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -341,7 +341,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'/, file)
+ assert_match(/config.session_store :encrypted_cookie_store, key: '_.+_session'/, file)
end
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 4b168ae110..9e7626647e 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -244,13 +244,9 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
/module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/
# Views
- %w(
- index
- edit
- new
- show
- _form
- ).each { |view| assert_file "app/views/test_app/product_lines/#{view}.html.erb" }
+ %w(index edit new show _form).each do |view|
+ assert_file "app/views/test_app/product_lines/#{view}.html.erb"
+ end
assert_no_file "app/views/layouts/test_app/product_lines.html.erb"
# Helpers
@@ -316,13 +312,9 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
/module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/
# Views
- %w(
- index
- edit
- new
- show
- _form
- ).each { |view| assert_file "app/views/test_app/admin/roles/#{view}.html.erb" }
+ %w(index edit new show _form).each do |view|
+ assert_file "app/views/test_app/admin/roles/#{view}.html.erb"
+ end
assert_no_file "app/views/layouts/admin/roles.html.erb"
# Helpers
@@ -389,13 +381,9 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
/module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/
# Views
- %w(
- index
- edit
- new
- show
- _form
- ).each { |view| assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" }
+ %w(index edit new show _form).each do |view|
+ assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb"
+ end
assert_no_file "app/views/layouts/admin/user/special/roles.html.erb"
# Helpers
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 6974db5751..ab78800a4e 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -66,6 +66,12 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
end
+ def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files
+ run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app']
+
+ assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
+ end
+
def test_ensure_that_plugin_options_are_not_passed_to_app_generator
FileUtils.cd(Rails.root)
assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]))
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 38454dfb8b..8cacca668f 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -63,12 +63,9 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
def test_views_are_generated
run_generator
- %w(
- index
- edit
- new
- show
- ).each { |view| assert_file "app/views/users/#{view}.html.erb" }
+ %w(index edit new show).each do |view|
+ assert_file "app/views/users/#{view}.html.erb"
+ end
assert_no_file "app/views/layouts/users.html.erb"
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index efe47cdfcb..54d5a9db6f 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -67,13 +67,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
# Views
- %w(
- index
- edit
- new
- show
- _form
- ).each { |view| assert_file "app/views/product_lines/#{view}.html.erb" }
+ %w(index edit new show _form).each do |view|
+ assert_file "app/views/product_lines/#{view}.html.erb"
+ end
assert_no_file "app/views/layouts/product_lines.html.erb"
# Helpers
@@ -187,13 +183,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
/class Admin::RolesControllerTest < ActionController::TestCase/
# Views
- %w(
- index
- edit
- new
- show
- _form
- ).each { |view| assert_file "app/views/admin/roles/#{view}.html.erb" }
+ %w(index edit new show _form).each do |view|
+ assert_file "app/views/admin/roles/#{view}.html.erb"
+ end
assert_no_file "app/views/layouts/admin/roles.html.erb"
# Helpers
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index e59488f97d..0cb65f8e0d 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -119,7 +119,7 @@ module TestHelpers
add_to_config <<-RUBY
config.eager_load = false
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.action_controller.allow_forgery_protection = false
@@ -138,7 +138,7 @@ module TestHelpers
app = Class.new(Rails::Application)
app.config.eager_load = false
- app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ app.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, key: "_myapp_session"
app.config.active_support.deprecation = :log