aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--actionpack/CHANGELOG.md46
-rw-r--r--actionpack/RUNNING_UNIT_TESTS.rdoc (renamed from actionpack/RUNNING_UNIT_TESTS)0
-rw-r--r--actionpack/actionpack.gemspec4
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb20
-rw-r--r--actionpack/lib/action_view/test_case.rb6
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb35
-rw-r--r--actionpack/test/dispatch/request_test.rb3
-rw-r--r--actionpack/test/dispatch/routing_test.rb61
-rw-r--r--actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_partial_inside_directory.html.erb1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb3
-rw-r--r--actionpack/test/template/test_case_test.rb21
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb117
-rw-r--r--activerecord/CHANGELOG.md56
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc (renamed from activerecord/RUNNING_UNIT_TESTS)0
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb7
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb13
-rw-r--r--activerecord/lib/active_record/store.rb33
-rw-r--r--activerecord/lib/active_record/transactions.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb14
-rw-r--r--activerecord/test/cases/associations/eager_test.rb6
-rw-r--r--activerecord/test/cases/persistence_test.rb6
-rw-r--r--activerecord/test/cases/quoting_test.rb14
-rw-r--r--activerecord/test/cases/relation/where_test.rb25
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb6
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb16
-rw-r--r--activerecord/test/cases/store_test.rb12
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb27
-rw-r--r--activerecord/test/models/admin/user.rb9
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activesupport/CHANGELOG.md16
-rw-r--r--activesupport/lib/active_support/gzip.rb4
-rw-r--r--activesupport/lib/active_support/i18n.rb3
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin904408 -> 904483 bytes
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb2
-rw-r--r--activesupport/test/gzip_test.rb18
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/engines.md20
-rw-r--r--guides/source/getting_started.md4
-rw-r--r--guides/source/routing.md13
-rw-r--r--guides/source/upgrading_ruby_on_rails.md10
-rw-r--r--railties/CHANGELOG.md17
-rw-r--r--railties/lib/rails/commands/application.rb8
-rw-r--r--railties/lib/rails/generators/app_base.rb10
-rw-r--r--railties/lib/rails/generators/migration.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb2
-rw-r--r--railties/lib/rails/ruby_version_check.rb4
-rw-r--r--railties/lib/rails/test_unit/testing.rake12
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb1
67 files changed, 682 insertions, 172 deletions
diff --git a/.gitignore b/.gitignore
index 854fdbf450..a3a5304ecd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,7 @@
debug.log
.Gemfile
/.bundle
-/.rbenv-version
-/.rvmrc
+/.ruby-version
/Gemfile.lock
/pkg
/dist
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 69ed117a03..6940683c8c 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,49 @@
## Rails 4.0.0 (unreleased) ##
+* Change `image_alt` method to replace underscores/hyphens to spaces in filenames.
+
+ Previously, underscored filenames became `alt="A_long_file_name_with_underscores"`
+ in HTML, which is poor for accessibility. For instance, Apple's VoiceOver Utility
+ pronounces each underscore. `A_long_file_name` thus would be read as `A underscore
+ long underscore file underscore name.` Now underscored or hyphenated filenames
+ (both of which are very popular naming conventions) read more naturally in
+ screen readers by converting both hyphens and underscores to spaces.
+
+ Before:
+ image_tag('underscored_file_name.png')
+ # => <img alt="Underscored_file_name" src="/assets/underscored_file_name.png" />
+
+ After:
+ image_tag('underscored_file_name.png')
+ # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" />
+
+ *Nick Cox*
+
+* We don't support the `:controller` option for route definitions
+ with the ruby constant notation. This will now result in an
+ `ArgumentError`.
+
+ Example:
+ # This raises an ArgumentError:
+ resources :posts, :controller => "Admin::Posts"
+
+ # Use directory notation instead:
+ resources :posts, :controller => "admin/posts"
+
+ *Yves Senn*
+
+* `assert_template` can be used to verify the locals of partials,
+ which live inside a directory.
+ Fixes #8516.
+
+ # Prefixed partials inside directories worked and still work.
+ assert_template partial: 'directory/_partial', locals: {name: 'John'}
+
+ # This did not work but does now.
+ assert_template partial: 'directory/partial', locals: {name: 'John'}
+
+ *Yves Senn*
+
* Fix `content_tag_for` with array html option.
It would embed array as string instead of joining it like `content_tag` does:
@@ -248,7 +292,7 @@
* More descriptive error messages when calling `render :partial` with
an invalid `:layout` argument.
-
+
Fixes #8376.
render partial: 'partial', layout: true
diff --git a/actionpack/RUNNING_UNIT_TESTS b/actionpack/RUNNING_UNIT_TESTS.rdoc
index 1b29abd2d1..1b29abd2d1 100644
--- a/actionpack/RUNNING_UNIT_TESTS
+++ b/actionpack/RUNNING_UNIT_TESTS.rdoc
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 70a1975ee9..03eeb841ee 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'builder', '~> 3.1.0'
- s.add_dependency 'rack', '~> 1.5.0'
- s.add_dependency 'rack-test', '~> 0.6.1'
+ s.add_dependency 'rack', '~> 1.5.2'
+ s.add_dependency 'rack-test', '~> 0.6.2'
s.add_dependency 'erubis', '~> 2.7.0'
s.add_development_dependency 'activemodel', version
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 77b173979e..17379cf7ac 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -126,7 +126,7 @@ module ActionController #:nodoc:
host = request.host
secure = request.ssl?
- new(key_generator, host, secure)
+ new(key_generator, host, secure, options_for_env({}))
end
def write(*)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 5ae5dd331a..bba1f1e201 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -126,7 +126,11 @@ module ActionController
if expected_partial = options[:partial]
if expected_locals = options[:locals]
if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/,'')
+ view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
+
+ partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
+ assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
+
msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
expected_locals,
@_rendered_views.locals_for(view)]
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index e7e8905d7e..ca1ace4537 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -170,7 +170,7 @@ module ActionDispatch # :nodoc:
alias_method :status_message, :message
def respond_to?(method)
- if method.to_sym == :to_path
+ if method.to_s == 'to_path'
stream.respond_to?(:to_path)
else
super
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 43f26d696d..e9ef9ee9f2 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -32,8 +32,12 @@ module ActionDispatch
params.reject! { |_,v| v.to_param.nil? }
result = build_host_url(options)
- if options[:trailing_slash] && !path.ends_with?('/')
- result << path.sub(/(\?|\z)/) { "/" + $& }
+ if options[:trailing_slash]
+ if path.include?('?')
+ result << path.sub(/\?/, '/\&')
+ else
+ result << path.sub(/[^\/]\z|\A\z/, '\&/')
+ end
else
result << path
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 0f02d230d4..fff26bd1b2 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/key_generator'
require 'active_support/message_verifier'
module ActionDispatch
@@ -110,13 +111,17 @@ module ActionDispatch
# $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
+ def self.options_for_env(env) #:nodoc:
+ { 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] }
+ end
+
def self.build(request)
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] }
+ options = options_for_env env
host = request.host
secure = request.ssl?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 82ef1d0333..0a41ed0fcf 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -246,6 +246,12 @@ module ActionDispatch
raise ArgumentError, "missing :action"
end
+ if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
+ message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
+ message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
+ raise ArgumentError, message
+ end
+
hash = {}
hash[:controller] = controller unless controller.blank?
hash[:action] = action unless action.blank?
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 5b3a2cae7c..31e37893c6 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -214,10 +214,24 @@ module ActionView
end
# Returns a string suitable for an html image tag alt attribute.
- # +src+ is meant to be an image file path.
- # It removes the basename of the file path and the digest, if any.
+ # The +src+ argument is meant to be an image file path.
+ # The method removes the basename of the file path and the digest,
+ # if any. It also removes hyphens and underscores from file names and
+ # replaces them with spaces, returning a space-separated, titleized
+ # string.
+ #
+ # ==== Examples
+ #
+ # image_tag('rails.png')
+ # # => <img alt="Rails" src="/assets/rails.png" />
+ #
+ # image_tag('hyphenated-file-name.png')
+ # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" />
+ #
+ # image_tag('underscored_file_name.png')
+ # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" />
def image_alt(src)
- File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize
+ File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
end
# Returns an html video tag for the +sources+. If +sources+ is a string,
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 4479da5bc4..463f192d0c 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -122,7 +122,7 @@ module ActionView
class RenderedViewsCollection
def initialize
- @rendered_views ||= {}
+ @rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
end
def add(view, locals)
@@ -134,6 +134,10 @@ module ActionView
@rendered_views[view]
end
+ def rendered_views
+ @rendered_views.keys
+ end
+
def view_rendered?(view, expected_locals)
locals_for(view).any? do |actual_locals|
expected_locals.all? {|key, value| value == actual_locals[key] }
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 7571192f97..c272e785c2 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -66,6 +66,19 @@ class RequestForgeryProtectionControllerUsingException < ActionController::Base
protect_from_forgery :only => %w(index meta), :with => :exception
end
+class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base
+ protect_from_forgery :with => :null_session
+
+ def signed
+ cookies.signed[:foo] = 'bar'
+ render :nothing => true
+ end
+
+ def encrypted
+ cookies.encrypted[:foo] = 'bar'
+ render :nothing => true
+ end
+end
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
self.allow_forgery_protection = false
@@ -287,6 +300,28 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController
end
end
+class NullSessionDummyKeyGenerator
+ def generate_key(secret)
+ '03312270731a2ed0d11ed091c2338a06'
+ end
+end
+
+class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
+ def setup
+ @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new
+ end
+
+ test 'should allow to set signed cookies' do
+ post :signed
+ assert_response :ok
+ end
+
+ test 'should allow to set encrypted cookies' do
+ post :encrypted
+ assert_response :ok
+ end
+end
+
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
include RequestForgeryProtectionTests
def assert_blocked
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 8a01b29340..91810864d5 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -13,6 +13,9 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '/books', url_for(:only_path => true, :path => '/books')
+ assert_equal 'http://www.example.com/books/?q=code', url_for(trailing_slash: true, path: '/books?q=code')
+ assert_equal 'http://www.example.com/books/?spareslashes=////', url_for(trailing_slash: true, path: '/books?spareslashes=////')
+
assert_equal 'http://www.example.com', url_for
assert_equal 'http://api.example.com', url_for(:subdomain => 'api')
assert_equal 'http://example.com', url_for(:subdomain => false)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 9f31ce8127..143733254b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1031,6 +1031,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'users/home#index', @response.body
end
+ def test_namespace_containing_numbers
+ draw do
+ namespace :v2 do
+ resources :subscriptions
+ end
+ end
+
+ get '/v2/subscriptions'
+ assert_equal 'v2/subscriptions#index', @response.body
+ assert_equal '/v2/subscriptions', v2_subscriptions_path
+ end
+
def test_articles_with_id
draw do
controller :articles do
@@ -2833,21 +2845,52 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
end
end
- DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new
- DefaultScopeRoutes.draw do
- namespace :admin do
- resources :storage_files, :controller => "StorageFiles"
- end
+ def draw(&block)
+ @app = ActionDispatch::Routing::RouteSet.new
+ @app.draw(&block)
end
- def app
- DefaultScopeRoutes
- end
+ def test_valid_controller_options_inside_namespace
+ draw do
+ namespace :admin do
+ resources :storage_files, :controller => "storage_files"
+ end
+ end
- def test_controller_options
get '/admin/storage_files'
assert_equal "admin/storage_files#index", @response.body
end
+
+ def test_resources_with_valid_namespaced_controller_option
+ draw do
+ resources :storage_files, :controller => 'admin/storage_files'
+ end
+
+ get 'storage_files'
+ assert_equal "admin/storage_files#index", @response.body
+ end
+
+ def test_warn_with_ruby_constant_syntax_controller_option
+ e = assert_raise(ArgumentError) do
+ draw do
+ namespace :admin do
+ resources :storage_files, :controller => "StorageFiles"
+ end
+ end
+ end
+
+ assert_match "'admin/StorageFiles' is not a supported controller name", e.message
+ end
+
+ def test_warn_with_ruby_constant_syntax_namespaced_controller_option
+ e = assert_raise(ArgumentError) do
+ draw do
+ resources :storage_files, :controller => 'Admin::StorageFiles'
+ end
+ end
+
+ assert_match "'Admin::StorageFiles' is not a supported controller name", e.message
+ end
end
class TestDefaultScope < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb
new file mode 100644
index 0000000000..1cc8d41475
--- /dev/null
+++ b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb
@@ -0,0 +1 @@
+Hello <%= name %>
diff --git a/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb
new file mode 100644
index 0000000000..1461b95186
--- /dev/null
+++ b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb
@@ -0,0 +1 @@
+<%= render partial: 'test/_directory/partial_with_locales', locals: {'name' => 'Jane'} %>
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 82c9d383ac..11614a45dc 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -443,7 +443,8 @@ class AssetTagHelperTest < ActionView::TestCase
[nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix|
assert_equal 'Rails', image_alt("#{prefix}rails.png")
assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png")
- assert_equal 'Avatar-0000', image_alt("#{prefix}avatar-0000.png")
+ assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png")
+ assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png")
end
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index c7231d9cd5..acd002ce73 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -329,6 +329,27 @@ module ActionView
assert_template partial: '_partial', locals: { 'second' => '2' }
end
+ test 'raises descriptive error message when template was not rendered' do
+ controller.controller_path = "test"
+ render(template: "test/hello_world_with_partial")
+ e = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_template partial: 'i_was_never_rendered', locals: { 'did_not' => 'happen' }
+ end
+ assert_match "i_was_never_rendered to be rendered but it was not.", e.message
+ assert_match 'Expected ["/test/partial"] to include "i_was_never_rendered"', e.message
+ end
+
+ test 'specifying locals works when the partial is inside a directory with underline prefix' do
+ controller.controller_path = "test"
+ render(template: 'test/render_partial_inside_directory')
+ assert_template partial: 'test/_directory/_partial_with_locales', locals: { 'name' => 'Jane' }
+ end
+
+ test 'specifying locals works when the partial is inside a directory without underline prefix' do
+ controller.controller_path = "test"
+ render(template: 'test/render_partial_inside_directory')
+ assert_template partial: 'test/_directory/partial_with_locales', locals: { 'name' => 'Jane' }
+ end
end
module AHelperWithInitialize
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 1a40ca8efc..8b2f886cc4 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -5,13 +5,12 @@ require 'models/topic'
require 'models/person'
class LengthValidationTest < ActiveModel::TestCase
-
def teardown
Topic.reset_callbacks(:validate)
end
def test_validates_length_of_with_allow_nil
- Topic.validates_length_of( :title, :is => 5, :allow_nil => true )
+ Topic.validates_length_of( :title, is: 5, allow_nil: true )
assert Topic.new("title" => "ab").invalid?
assert Topic.new("title" => "").invalid?
@@ -20,7 +19,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_allow_blank
- Topic.validates_length_of( :title, :is => 5, :allow_blank => true )
+ Topic.validates_length_of( :title, is: 5, allow_blank: true )
assert Topic.new("title" => "ab").invalid?
assert Topic.new("title" => "").valid?
@@ -29,7 +28,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_minimum
- Topic.validates_length_of :title, :minimum => 5
+ Topic.validates_length_of :title, minimum: 5
t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
@@ -51,13 +50,13 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_maximum_should_allow_nil
- Topic.validates_length_of :title, :maximum => 10
+ Topic.validates_length_of :title, maximum: 10
t = Topic.new
assert t.valid?
end
def test_optionally_validates_length_of_using_minimum
- Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
+ Topic.validates_length_of :title, minimum: 5, allow_nil: true
t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
@@ -67,7 +66,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_maximum
- Topic.validates_length_of :title, :maximum => 5
+ Topic.validates_length_of :title, maximum: 5
t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
@@ -82,7 +81,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_optionally_validates_length_of_using_maximum
- Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
+ Topic.validates_length_of :title, maximum: 5, allow_nil: true
t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
@@ -92,7 +91,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_within
- Topic.validates_length_of(:title, :content, :within => 3..5)
+ Topic.validates_length_of(:title, :content, within: 3..5)
t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
assert t.invalid?
@@ -111,7 +110,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_within_with_exclusive_range
- Topic.validates_length_of(:title, :within => 4...10)
+ Topic.validates_length_of(:title, within: 4...10)
t = Topic.new("title" => "9 chars!!")
assert t.valid?
@@ -125,7 +124,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_optionally_validates_length_of_using_within
- Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
+ Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true
t = Topic.new('title' => 'abc', 'content' => 'abcd')
assert t.valid?
@@ -135,7 +134,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_is
- Topic.validates_length_of :title, :is => 5
+ Topic.validates_length_of :title, is: 5
t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
@@ -153,7 +152,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_optionally_validates_length_of_using_is
- Topic.validates_length_of :title, :is => 5, :allow_nil => true
+ Topic.validates_length_of :title, is: 5, allow_nil: true
t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
@@ -167,25 +166,25 @@ class LengthValidationTest < ActiveModel::TestCase
bigmax = 2 ** 32
bigrange = bigmin...bigmax
assert_nothing_raised do
- Topic.validates_length_of :title, :is => bigmin + 5
- Topic.validates_length_of :title, :within => bigrange
- Topic.validates_length_of :title, :in => bigrange
- Topic.validates_length_of :title, :minimum => bigmin
- Topic.validates_length_of :title, :maximum => bigmax
+ Topic.validates_length_of :title, is: bigmin + 5
+ Topic.validates_length_of :title, within: bigrange
+ Topic.validates_length_of :title, in: bigrange
+ Topic.validates_length_of :title, minimum: bigmin
+ Topic.validates_length_of :title, maximum: bigmax
end
end
def test_validates_length_of_nasty_params
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => -6) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => 6) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum => "a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum => "a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => "a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: -6) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: 6) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, minimum: "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, maximum: "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: "a") }
end
def test_validates_length_of_custom_errors_for_minimum_with_message
- Topic.validates_length_of( :title, :minimum => 5, :message => "boo %{count}" )
+ Topic.validates_length_of( :title, minimum: 5, message: "boo %{count}" )
t = Topic.new("title" => "uhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -193,7 +192,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_minimum_with_too_short
- Topic.validates_length_of( :title, :minimum => 5, :too_short => "hoo %{count}" )
+ Topic.validates_length_of( :title, minimum: 5, too_short: "hoo %{count}" )
t = Topic.new("title" => "uhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -201,7 +200,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_message
- Topic.validates_length_of( :title, :maximum => 5, :message => "boo %{count}" )
+ Topic.validates_length_of( :title, maximum: 5, message: "boo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -209,7 +208,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_in
- Topic.validates_length_of(:title, :in => 10..20, :message => "hoo %{count}")
+ Topic.validates_length_of(:title, in: 10..20, message: "hoo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -222,7 +221,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_too_long
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}" )
+ Topic.validates_length_of( :title, maximum: 5, too_long: "hoo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -230,21 +229,21 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_both_too_short_and_too_long
- Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long'
+ Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: 'too short', too_long: 'too long'
- t = Topic.new(:title => 'a')
+ t = Topic.new(title: 'a')
assert t.invalid?
assert t.errors[:title].any?
assert_equal ['too short'], t.errors['title']
- t = Topic.new(:title => 'aaaaaa')
+ t = Topic.new(title: 'aaaaaa')
assert t.invalid?
assert t.errors[:title].any?
assert_equal ['too long'], t.errors['title']
end
def test_validates_length_of_custom_errors_for_is_with_message
- Topic.validates_length_of( :title, :is => 5, :message => "boo %{count}" )
+ Topic.validates_length_of( :title, is: 5, message: "boo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -252,7 +251,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_wrong_length
- Topic.validates_length_of( :title, :is => 5, :wrong_length => "hoo %{count}" )
+ Topic.validates_length_of( :title, is: 5, wrong_length: "hoo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -260,7 +259,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_minimum_utf8
- Topic.validates_length_of :title, :minimum => 5
+ Topic.validates_length_of :title, minimum: 5
t = Topic.new("title" => "一二三四五", "content" => "whatever")
assert t.valid?
@@ -272,7 +271,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_maximum_utf8
- Topic.validates_length_of :title, :maximum => 5
+ Topic.validates_length_of :title, maximum: 5
t = Topic.new("title" => "一二三四五", "content" => "whatever")
assert t.valid?
@@ -284,7 +283,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_within_utf8
- Topic.validates_length_of(:title, :content, :within => 3..5)
+ Topic.validates_length_of(:title, :content, within: 3..5)
t = Topic.new("title" => "一二", "content" => "12三四五六七")
assert t.invalid?
@@ -296,12 +295,12 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_optionally_validates_length_of_using_within_utf8
- Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
+ Topic.validates_length_of :title, within: 3..5, allow_nil: true
- t = Topic.new(:title => "一二三四五")
+ t = Topic.new(title: "一二三四五")
assert t.valid?, t.errors.inspect
- t = Topic.new(:title => "一二三")
+ t = Topic.new(title: "一二三")
assert t.valid?, t.errors.inspect
t.title = nil
@@ -309,7 +308,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_is_utf8
- Topic.validates_length_of :title, :is => 5
+ Topic.validates_length_of :title, is: 5
t = Topic.new("title" => "一二345", "content" => "whatever")
assert t.valid?
@@ -321,9 +320,9 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_block
- Topic.validates_length_of :content, :minimum => 5, :too_short => "Your essay must be at least %{count} words.",
- :tokenizer => lambda {|str| str.scan(/\w+/) }
- t = Topic.new(:content => "this content should be long enough")
+ Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.",
+ tokenizer: lambda {|str| str.scan(/\w+/) }
+ t = Topic.new(content: "this content should be long enough")
assert t.valid?
t.content = "not long enough"
@@ -333,18 +332,18 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_for_fixnum
- Topic.validates_length_of(:approved, :is => 4)
+ Topic.validates_length_of(:approved, is: 4)
- t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1)
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1)
assert t.invalid?
assert t.errors[:approved].any?
- t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1234)
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1234)
assert t.valid?
end
def test_validates_length_of_for_ruby_class
- Person.validates_length_of :karma, :minimum => 5
+ Person.validates_length_of :karma, minimum: 5
p = Person.new
p.karma = "Pix"
@@ -359,7 +358,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_for_infinite_maxima
- Topic.validates_length_of(:title, :within => 5..Float::INFINITY)
+ Topic.validates_length_of(:title, within: 5..Float::INFINITY)
t = Topic.new("title" => "1234")
assert t.invalid?
@@ -368,7 +367,7 @@ class LengthValidationTest < ActiveModel::TestCase
t.title = "12345"
assert t.valid?
- Topic.validates_length_of(:author_name, :maximum => Float::INFINITY)
+ Topic.validates_length_of(:author_name, maximum: Float::INFINITY)
assert t.valid?
@@ -377,13 +376,13 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed
- Topic.validates_length_of :title, :maximum => 10, :allow_nil => false
+ Topic.validates_length_of :title, maximum: 10, allow_nil: false
t = Topic.new
assert t.invalid?
end
def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed
- Topic.validates_length_of :title, :maximum => 10, :allow_blank => false
+ Topic.validates_length_of :title, maximum: 10, allow_blank: false
t = Topic.new
assert t.invalid?
@@ -392,13 +391,13 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil
- Topic.validates_length_of :title, :minimum => 5, :maximum => 10
+ Topic.validates_length_of :title, minimum: 5, maximum: 10
t = Topic.new
assert t.invalid?
end
def test_validates_length_of_using_minimum_0_should_not_allow_nil
- Topic.validates_length_of :title, :minimum => 0
+ Topic.validates_length_of :title, minimum: 0
t = Topic.new
assert t.invalid?
@@ -407,11 +406,19 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_is_0_should_not_allow_nil
- Topic.validates_length_of :title, :is => 0
+ Topic.validates_length_of :title, is: 0
t = Topic.new
assert t.invalid?
t.title = ""
assert t.valid?
end
+
+ def test_validates_with_diff_in_option
+ Topic.validates_length_of(:title, is: 5)
+ Topic.validates_length_of(:title, is: 5, if: Proc.new { false } )
+
+ assert Topic.new("title" => "david").valid?
+ assert Topic.new("title" => "david2").invalid?
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index f8931677ed..4489ca1aff 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,59 @@
## Rails 4.0.0 (unreleased) ##
+* Preloading `has_many :through` associations with conditions won't
+ cache the `:through` association. This will prevent invalid
+ subsets to be cached.
+ Fixes #8423.
+
+ Example:
+
+ class User
+ has_many :posts
+ has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts
+ end
+
+ a_user = User.includes(:recent_comments).first
+
+ # this is preloaded
+ a_user.recent_comments
+
+ # fetching the recent_comments through the posts association won't preload it.
+ a_user.posts
+
+ *Yves Senn*
+
+* Don't run after_commit callback when creating through an association
+ if saving the record fails.
+
+ *James Miller *
+
+* Allow store accessors to be overrided like other attribute methods, e.g.:
+
+ class User < ActiveRecord::Base
+ store :settings, accessors: [ :color, :homepage ], coder: JSON
+
+ def color
+ super || 'red'
+ end
+ end
+
+ *Sergey Nartimov*
+
+* Quote numeric values being compared to non-numeric columns. Otherwise,
+ in some database, the string column values will be coerced to a numeric
+ allowing 0, 0.0 or false to match any string starting with a non-digit.
+
+ Example:
+
+ App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0'
+
+ *Dylan Smith*
+
+* Schema dumper supports dumping the enabled database extensions to `schema.rb`
+ (currently only supported by postgresql).
+
+ *Justin George*
+
* The `DATABASE_URL` environment variable now converts ints, floats, and
the strings true and false to Ruby types. For example, SQLite requires
that the timeout value is an integer, and PostgreSQL requires that the
@@ -591,7 +645,7 @@
After:
- #=> SELECT * FROM users WHERE 1 = 2;
+ #=> SELECT * FROM users WHERE 1=0;
*Damien Mathieu*
diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS.rdoc
index bdd8834dcb..bdd8834dcb 100644
--- a/activerecord/RUNNING_UNIT_TESTS
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 1c1ba11c44..b0b1c13b0d 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -31,7 +31,8 @@ module ActiveRecord
through_records = Array.wrap(owner.send(through_reflection.name))
# Dont cache the association - we would only be caching a subset
- if reflection.options[:source_type] && through_reflection.collection?
+ if (through_scope != through_reflection.klass.unscoped) ||
+ (reflection.options[:source_type] && through_reflection.collection?)
owner.association(through_reflection.name).reset
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index d18b9c991f..aec4654eee 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -25,13 +25,19 @@ module ActiveRecord
when true, false
if column && column.type == :integer
value ? '1' : '0'
+ elsif column && [:text, :string, :binary].include?(column.type)
+ value ? "'1'" : "'0'"
else
value ? quoted_true : quoted_false
end
# BigDecimals need to be put in a non-normalized form and quoted.
when nil then "NULL"
- when BigDecimal then value.to_s('F')
- when Numeric, ActiveSupport::Duration then value.to_s
+ when Numeric, ActiveSupport::Duration
+ value = BigDecimal === value ? value.to_s('F') : value.to_s
+ if column && ![:integer, :float, :decimal].include?(column.type)
+ value = "'#{value}'"
+ end
+ value
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value.to_s}'"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index b1ec33d06c..f758e19a4f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -252,15 +252,12 @@ module ActiveRecord
self
end
- %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
- class_eval <<-EOV, __FILE__, __LINE__ + 1
- def #{column_type}(*args) # def string(*args)
- options = args.extract_options! # options = args.extract_options!
- column_names = args # column_names = args
- type = :'#{column_type}' # type = :string
- column_names.each { |name| column(name, type, options) } # column_names.each { |name| column(name, type, options) }
- end # end
- EOV
+ [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
+ define_method column_type do |*args|
+ options = args.extract_options!
+ column_names = args
+ column_names.each { |name| column(name, column_type, options) }
+ end
end
# Adds index options to the indexes hash, keyed by column name
@@ -486,15 +483,13 @@ module ActiveRecord
#
# t.string(:goat)
# t.string(:goat, :sheep)
- %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
- class_eval <<-EOV, __FILE__, __LINE__ + 1
- def #{column_type}(*args) # def string(*args)
- options = args.extract_options! # options = args.extract_options!
- args.each do |name| # column_names.each do |name|
- @base.add_column(@table_name, name, :#{column_type}, options) # @base.add_column(@table_name, name, :string, options)
- end # end
- end # end
- EOV
+ [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
+ define_method column_type do |*args|
+ options = args.extract_options!
+ args.each do |name|
+ @base.add_column(@table_name, name, column_type, options)
+ end
+ end
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index b2ad4e600d..eecf4faa5d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -177,10 +177,16 @@ module ActiveRecord
false
end
+ # A list of extensions, to be filled in by databases that
+ # support them (at the moment, postgresql).
+ def extensions
+ []
+ end
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
- # +binds+
+ # +binds+.
def substitute_at(column, index)
Arel::Nodes::BindParam.new '?'
end
@@ -320,6 +326,10 @@ module ActiveRecord
# override in derived class
ActiveRecord::StatementInvalid.new(message)
end
+
+ def valid_types?(type)
+ true
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 52b0b3fe79..de5232f960 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -212,8 +212,6 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
s = column.class.string_to_binary(value).unpack("H*")[0]
"x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
else
super
end
@@ -750,6 +748,9 @@ module ActiveRecord
execute("SET #{encoding} #{variable_assignments}", :skip_logging)
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 747331f3a1..a4b3a0c584 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -74,12 +74,13 @@ module ActiveRecord
def type_cast_for_write(value)
return value unless number?
- if value == false
+ case value
+ when FalseClass
0
- elsif value == true
+ when TrueClass
1
- elsif value.is_a?(String) && value.blank?
- nil
+ when String
+ value.presence
else
value
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 5ce2f1b04c..271a6848ee 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -605,6 +605,15 @@ module ActiveRecord
end
end
+ def extensions
+ if supports_extensions?
+ res = exec_query "SELECT extname from pg_extension", "SCHEMA"
+ res.rows.map { |r| res.column_types['extname'].type_cast r.first }
+ else
+ super
+ end
+ end
+
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
@@ -878,6 +887,10 @@ module ActiveRecord
def table_definition
TableDefinition.new(self)
end
+
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7bead4bde9..b644e7bd60 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -603,6 +603,10 @@ module ActiveRecord
end
end
+ def valid_type?(type)
+ true
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 537ebbef28..5cd015eba7 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -8,7 +8,7 @@ module ActiveRecord
if value.is_a?(Hash)
if value.empty?
- queries << '1 = 2'
+ queries << '1=0'
else
table = Arel::Table.new(column, default_table.engine)
association = klass.reflect_on_association(column.to_sym)
@@ -98,6 +98,11 @@ module ActiveRecord
when Class
# FIXME: I think we need to deprecate this behavior
attribute.eq(value.name)
+ when Integer, ActiveSupport::Duration
+ # Arel treats integers as literals, but they should be quoted when compared with strings
+ table = attribute.relation
+ column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s]
+ attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column)))
else
attribute.eq(value)
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 36bde44e7c..df090b972d 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -24,6 +24,7 @@ module ActiveRecord
def dump(stream)
header(stream)
+ extensions(stream)
tables(stream)
trailer(stream)
stream
@@ -66,6 +67,18 @@ HEADER
stream.puts "end"
end
+ def extensions(stream)
+ return unless @connection.supports_extensions?
+ extensions = @connection.extensions
+ if extensions.any?
+ stream.puts " # These are extensions that must be enabled in order to support this database"
+ extensions.each do |extension|
+ stream.puts " enable_extension #{extension.inspect}"
+ end
+ stream.puts
+ end
+ end
+
def tables(stream)
@connection.tables.sort.each do |tbl|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index cf4cf9e602..a610f479f2 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -42,21 +42,19 @@ module ActiveRecord
#
# All stored values are automatically available through accessors on the Active Record
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
- # the default accessors (using the same name as the attribute) and calling
- # <tt>read_store_attribute(store_attribute_name, attr_name)</tt> and
- # <tt>write_store_attribute(store_attribute_name, attr_name, value)</tt> to actually
- # change things.
+ # the default accessors (using the same name as the attribute) and calling <tt>super</tt>
+ # to actually change things.
#
# class Song < ActiveRecord::Base
# # Uses a stored integer to hold the volume adjustment of the song
# store :settings, accessors: [:volume_adjustment]
#
# def volume_adjustment=(decibels)
- # write_store_attribute(:settings, :volume_adjustment, decibels.to_i)
+ # super(decibels.to_i)
# end
#
# def volume_adjustment
- # read_store_attribute(:settings, :volume_adjustment).to_i
+ # super.to_i
# end
# end
module Store
@@ -75,19 +73,30 @@ module ActiveRecord
def store_accessor(store_attribute, *keys)
keys = keys.flatten
- keys.each do |key|
- define_method("#{key}=") do |value|
- write_store_attribute(store_attribute, key, value)
- end
- define_method(key) do
- read_store_attribute(store_attribute, key)
+ _store_accessors_module.module_eval do
+ keys.each do |key|
+ define_method("#{key}=") do |value|
+ write_store_attribute(store_attribute, key, value)
+ end
+
+ define_method(key) do
+ read_store_attribute(store_attribute, key)
+ end
end
end
self.stored_attributes[store_attribute] ||= []
self.stored_attributes[store_attribute] |= keys
end
+
+ def _store_accessors_module
+ @_store_accessors_module ||= begin
+ mod = Module.new
+ include mod
+ mod
+ end
+ end
end
protected
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4a608e4f7b..4b7a388dc7 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -286,8 +286,11 @@ module ActiveRecord
end
# Call the after_commit callbacks
+ #
+ # Ensure that it is not called if the object was never persisted (failed create),
+ # but call it after the commit of a destroyed object
def committed! #:nodoc:
- run_callbacks :commit
+ run_callbacks :commit if destroyed? || persisted?
ensure
clear_transaction_record_state
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 7351ed9013..33c796191e 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -545,13 +545,13 @@ _SQL
def test_update_bit_string
new_bit_string = '11111111'
- new_bit_string_varying = 'FF'
+ new_bit_string_varying = '11111110'
assert @first_bit_string.bit_string = new_bit_string
assert @first_bit_string.bit_string_varying = new_bit_string_varying
assert @first_bit_string.save
assert @first_bit_string.reload
- assert_equal @first_bit_string.bit_string, new_bit_string
- assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
+ assert_equal new_bit_string, @first_bit_string.bit_string
+ assert_equal new_bit_string_varying, @first_bit_string.bit_string_varying
end
def test_update_oid
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 9498c829dc..6640f9b497 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -11,11 +11,18 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+
+ unless @connection.supports_extensions?
+ return skip "do not test on PG without hstore"
+ end
+
unless @connection.extension_enabled?('hstore')
@connection.enable_extension 'hstore'
- return skip "do not test on PG without hstore"
+ @connection.commit_db_transaction
end
+ @connection.reconnect!
+
@connection.transaction do
@connection.create_table('hstores') do |t|
t.hstore 'tags', :default => ''
@@ -28,6 +35,11 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
@connection.execute 'drop table if exists hstores'
end
+ def test_hstore_included_in_extensions
+ assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
+ assert @connection.extensions.include?('hstore'), "extension list should include hstore"
+ end
+
def test_hstore_enabled
assert @connection.extension_enabled?('hstore')
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index be2d30641e..3bf9125013 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1162,4 +1162,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post }
)
end
+
+ test "preloading does not cache has many association subset when preloaded with a through association" do
+ author = Author.includes(:comments_with_order_and_conditions, :posts).first
+ assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size }
+ assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" }
+ end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b936cca875..08dbf19e7b 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -399,6 +399,12 @@ class PersistencesTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
end
+ def test_string_ids
+ mv = Minivan.where(:minivan_id => 1234).first_or_initialize
+ assert mv.new_record?
+ assert_equal '1234', mv.minivan_id
+ end
+
def test_update_attribute_with_one_updated
t = Topic.first
t.update_attribute(:title, 'super_title')
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 3dd11ae89d..0ad05223d4 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -122,35 +122,35 @@ module ActiveRecord
def test_quote_float
float = 1.2
assert_equal float.to_s, @quoter.quote(float, nil)
- assert_equal float.to_s, @quoter.quote(float, Object.new)
+ assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float))
end
def test_quote_fixnum
fixnum = 1
assert_equal fixnum.to_s, @quoter.quote(fixnum, nil)
- assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new)
+ assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer))
end
def test_quote_bignum
bignum = 1 << 100
assert_equal bignum.to_s, @quoter.quote(bignum, nil)
- assert_equal bignum.to_s, @quoter.quote(bignum, Object.new)
+ assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer))
end
def test_quote_bigdecimal
bigdec = BigDecimal.new((1 << 100).to_s)
assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil)
- assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, Object.new)
+ assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal))
end
def test_dates_and_times
@quoter.extend(Module.new { def quoted_date(value) 'lol' end })
assert_equal "'lol'", @quoter.quote(Date.today, nil)
- assert_equal "'lol'", @quoter.quote(Date.today, Object.new)
+ assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date))
assert_equal "'lol'", @quoter.quote(Time.now, nil)
- assert_equal "'lol'", @quoter.quote(Time.now, Object.new)
+ assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time))
assert_equal "'lol'", @quoter.quote(DateTime.now, nil)
- assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new)
+ assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime))
end
def test_crazy_object
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index c43c7601a2..53cdf89b1f 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -108,5 +108,30 @@ module ActiveRecord
assert_equal 4, Edge.where(blank).order("sink_id").to_a.size
end
end
+
+ def test_where_with_integer_for_string_column
+ count = Post.where(:title => 0).count
+ assert_equal 0, count
+ end
+
+ def test_where_with_float_for_string_column
+ count = Post.where(:title => 0.0).count
+ assert_equal 0, count
+ end
+
+ def test_where_with_boolean_for_string_column
+ count = Post.where(:title => false).count
+ assert_equal 0, count
+ end
+
+ def test_where_with_decimal_for_string_column
+ count = Post.where(:title => BigDecimal.new(0)).count
+ assert_equal 0, count
+ end
+
+ def test_where_with_duration_for_string_column
+ count = Post.where(:title => 0.seconds).count
+ assert_equal 0, count
+ end
end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 7388324a0d..8e6c38706f 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -391,19 +391,19 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal Arel.sql("50000"), wheres[:salary]
end
def test_default_scope_with_module_includes
wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash
assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal Arel.sql("50000"), wheres[:salary]
end
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash
assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal Arel.sql("50000"), wheres[:salary]
end
def test_scope_overwrites_default
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index cae12e0e3a..bfecc0d1e9 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -1,6 +1,5 @@
require "cases/helper"
-
class SchemaDumperTest < ActiveRecord::TestCase
def setup
super
@@ -231,6 +230,21 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_extensions
+ connection = ActiveRecord::Base.connection
+ skip unless connection.supports_extensions?
+
+ connection.stubs(:extensions).returns(['hstore'])
+ output = standard_dump
+ assert_match "# These are extensions that must be enabled", output
+ assert_match %r{enable_extension "hstore"}, output
+
+ connection.stubs(:extensions).returns([])
+ output = standard_dump
+ assert_no_match "# These are extensions that must be enabled", output
+ assert_no_match %r{enable_extension}, output
+ end
+
def test_schema_dump_includes_xml_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_xml_data_type"} =~ output
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 43bf285ba9..3e32d866ee 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -35,6 +35,12 @@ class StoreTest < ActiveRecord::TestCase
assert_equal '(123) 456-7890', @john.phone_number
end
+ test "overriding a read accessor using super" do
+ @john.settings[:color] = nil
+
+ assert_equal 'red', @john.color
+ end
+
test "updating the store will mark it as changed" do
@john.color = 'red'
assert @john.settings_changed?
@@ -66,6 +72,12 @@ class StoreTest < ActiveRecord::TestCase
assert_equal '1234567890', @john.settings[:phone_number]
end
+ test "overriding a write accessor using super" do
+ @john.color = 'yellow'
+
+ assert_equal 'blue', @john.color
+ end
+
test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do
@john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy')
@john.height = 'low'
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 869892e33f..fd5651b4e0 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -5,9 +5,29 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
fixtures :topics
+ class ReplyWithCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+
+ belongs_to :topic, foreign_key: "parent_id"
+
+ validates_presence_of :content
+
+ after_commit :do_after_commit, on: :create
+
+ def history
+ @history ||= []
+ end
+
+ def do_after_commit
+ history << :commit_on_create
+ end
+ end
+
class TopicWithCallbacks < ActiveRecord::Base
self.table_name = :topics
+ has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
+
after_commit{|record| record.send(:do_after_commit, nil)}
after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
after_commit(:on => :update){|record| record.send(:do_after_commit, :update)}
@@ -93,6 +113,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:commit_on_create], @new_record.history
end
+ def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association
+ topic = TopicWithCallbacks.create!(:title => "New topic", :written_on => Date.today)
+ reply = topic.replies.create
+
+ assert_equal [], reply.history
+ end
+
def test_call_after_rollback_after_transaction_rollsback
@first.after_commit_block{|r| r.history << :after_commit}
@first.after_rollback_block{|r| r.history << :after_rollback}
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index 024fede266..4c3b71e8f9 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -27,4 +27,13 @@ class Admin::User < ActiveRecord::Base
def phone_number=(value)
write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,''))
end
+
+ def color
+ super || 'red'
+ end
+
+ def color=(value)
+ value = 'blue' unless %w(black red green blue).include?(value)
+ super
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index cd9835259a..d789b6cb7a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -540,6 +540,8 @@ ActiveRecord::Schema.define do
create_table :price_estimates, :force => true do |t|
t.string :estimate_of_type
t.integer :estimate_of_id
+ t.string :thing_type
+ t.integer :thing_id
t.integer :price
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index a2e6c62c3e..5f7559b5a6 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,17 @@
## Rails 4.0.0 (unreleased) ##
+* ActiveSupport::Gzip.compress allows two optional arguments for compression
+ level and strategy.
+
+ *Beyond*
+
+* Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy
+ by default, which is optional as per the ISO8601 spec, but extremely useful. Also
+ the default behaviour of Date#toJSON() in recent versions of Chrome, Safari and
+ Firefox.
+
+ *James Harton*
+
* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset*
* Standardise on `to_time` returning an instance of `Time` in the local system timezone
@@ -14,8 +26,8 @@
*Yves Senn*
-* Hash.from_xml raises when it encounters type="symbol" or type="yaml".
- Use Hash.from_trusted_xml to parse this XML.
+* `Hash.from_xml` raises when it encounters `type="symbol"` or `type="yaml"`.
+ Use `Hash.from_trusted_xml` to parse this XML.
CVE-2013-0156
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 6ef33ab683..b837c879bb 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -25,9 +25,9 @@ module ActiveSupport
end
# Compresses a string using gzip.
- def self.compress(source)
+ def self.compress(source, level=Zlib::DEFAULT_COMPRESSION, strategy=Zlib::DEFAULT_STRATEGY)
output = Stream.new
- gz = Zlib::GzipWriter.new(output)
+ gz = Zlib::GzipWriter.new(output, level, strategy)
gz.write(source)
gz.close
output.string
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index 188653bd9b..22521a8e93 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -1,4 +1,7 @@
begin
+ require 'active_support/core_ext/hash/deep_merge'
+ require 'active_support/core_ext/hash/except'
+ require 'active_support/core_ext/hash/slice'
require 'i18n'
require 'active_support/lazy_load_hooks'
rescue LoadError => e
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index f49ca47f14..cbc1608349 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -11,7 +11,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '6.1.0'
+ UNICODE_VERSION = '6.2.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index ff13efa990..0e6d12a186 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -154,7 +154,7 @@ module ActiveSupport
# # => "2005/02/01 15:15:10 +0000"
def as_json(options = nil)
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
- xmlschema
+ xmlschema(3)
else
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index df17a8cccf..2571faa019 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 18eca4cd8b..c2b3676aac 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -75,7 +75,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_to_json_with_use_standard_json_time_format_config_set_to_true
old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true
- assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(@twz)
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz)
ensure
ActiveSupport.use_standard_json_time_format = old
end
diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb
index 75a0505899..0e3cf3b429 100644
--- a/activesupport/test/gzip_test.rb
+++ b/activesupport/test/gzip_test.rb
@@ -4,6 +4,12 @@ require 'active_support/core_ext/object/blank'
class GzipTest < ActiveSupport::TestCase
def test_compress_should_decompress_to_the_same_value
assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World"))
+ assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::NO_COMPRESSION))
+ assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_SPEED))
+ assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_COMPRESSION))
+ assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::FILTERED))
+ assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::HUFFMAN_ONLY))
+ assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, nil))
end
def test_compress_should_return_a_binary_string
@@ -12,4 +18,16 @@ class GzipTest < ActiveSupport::TestCase
assert_equal Encoding.find('binary'), compressed.encoding
assert !compressed.blank?, "a compressed blank string should not be blank"
end
+
+ def test_compress_should_return_gzipped_string_by_compression_level
+ source_string = "Hello World"*100
+
+ gzipped_by_speed = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_SPEED)
+ assert_equal 1, Zlib::GzipReader.new(StringIO.new(gzipped_by_speed)).level
+
+ gzipped_by_best_compression = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_COMPRESSION)
+ assert_equal 9, Zlib::GzipReader.new(StringIO.new(gzipped_by_best_compression)).level
+
+ assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize)
+ end
end
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index a678dd9d90..9c157ec0b3 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -3,7 +3,7 @@ Ruby on Rails 4.0 Release Notes
Highlights in Rails 4.0:
-* Ruby 1.9.3 only
+* Ruby 2.0 preferred; 1.9.3+ required
* Strong Parameters
* Turbolinks
* Russian Doll Caching
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 459aa8d57e..00939c4ff2 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -28,7 +28,7 @@ Engines can also be isolated from their host applications. This means that an ap
It's important to keep in mind at all times that the application should **always** take precedence over its engines. An application is the object that has final say in what goes on in the universe (with the universe being the application's environment) where the engine should only be enhancing it, rather than changing it drastically.
-To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/resolve/refinerycms), a CMS engine.
+To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine.
Finally, engines would not have been possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks!
@@ -57,7 +57,7 @@ The `--full` option tells the generator that you want to create an engine, inclu
end
```
* A file at `lib/blorgh/engine.rb` which is identical in function to a standard Rails application's `config/application.rb` file:
-
+
```ruby
module Blorgh
class Engine < ::Rails::Engine
@@ -72,12 +72,12 @@ The `--mountable` option tells the generator that you want to create a "mountabl
* A namespaced `ApplicationHelper` stub
* A layout view template for the engine
* Namespace isolation to `config/routes.rb`:
-
+
```ruby
Blorgh::Engine.routes.draw do
end
```
-
+
* Namespace isolation to `lib/blorgh/engine.rb`:
```ruby
@@ -395,7 +395,7 @@ def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
flash[:notice] = "Comment has been created!"
- redirect_to post_path
+ redirect_to posts_path
end
```
@@ -650,6 +650,14 @@ self.author = Blorgh.user_class.find_or_create_by(name: author_name)
Resulting in something a little shorter, and more implicit in its behavior. The `user_class` method should always return a `Class` object.
+Since we changed the `user_class` method to no longer return a
+`String` but a `Class` we must also modify our `belongs_to` definition
+in the `Blorgh::Post` model:
+
+```ruby
+belongs_to :author, class_name: Blorgh.user_class.to_s
+```
+
To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing.
Create a new initializer at `config/initializers/blorgh.rb` inside the application where the `blorgh` engine is installed and put this content in it:
@@ -789,7 +797,7 @@ module Blorgh::Concerns::Models::Post
extend ActiveSupport::Concern
# 'included do' causes the included code to be evaluated in the
- # context where it is included (post.rb), rather than be
+ # context where it is included (post.rb), rather than be
# executed in the module's context (blorgh/concerns/models/post).
included do
attr_accessor :author_name
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index c394f30c38..87f5e43157 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -21,7 +21,7 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or higher
+* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer
* The [RubyGems](http://rubygems.org/) packaging system
* To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1)
* A working installation of the [SQLite3 Database](http://www.sqlite.org)
@@ -84,7 +84,7 @@ current version of Ruby installed:
```bash
$ ruby -v
-ruby 1.9.3p327
+ruby 1.9.3p385
```
To install Rails, use the `gem install` command provided by RubyGems:
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 14f23d4020..4614169653 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -832,6 +832,19 @@ will recognize incoming paths beginning with `/photos` but route to the `Images`
NOTE: Use `photos_path`, `new_photo_path`, etc. to generate paths for this resource.
+For namespaced controllers you can use the directory notation. For example:
+
+```ruby
+resources :user_permissions, controller: 'admin/user_permissions'
+```
+
+This will route to the `Admin::UserPermissions` controller.
+
+NOTE: Only the directory notation is supported. specifying the
+controller with ruby constant notation (eg. `:controller =>
+'Admin::UserPermissions'`) can lead to routing problems and results in
+a warning.
+
### Specifying Constraints
You can use the `:constraints` option to specify a required format on the implicit `id`. For example:
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index ea04036663..568767d9de 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -16,11 +16,11 @@ The best way to be sure that your application still works after upgrading is to
Rails generally stays close to the latest released Ruby version when it's released:
-* Rails 3 and above requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible.
-* Rails 3.2.x will be the last branch to support Ruby 1.8.7.
-* Rails 4 will support Ruby 1.9.3 or higher.
+* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible.
+* Rails 3.2.x is the last branch to support Ruby 1.8.7.
+* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer.
-TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
+TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
@@ -82,6 +82,8 @@ becomes
get 'こんにちは', controller: 'welcome', action: 'index'
```
+* Rails 4.0 has removed ActionDispatch::BestStandardsSupport middleware, !DOCTYPE html already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers`
+
### Active Support
Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index f86baee4c3..997858b3c5 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,11 +1,22 @@
## Rails 4.0.0 (unreleased) ##
-* Add --rc option to support the load of a custom rc file during the generation of a new app.
+* Add notice message for destroy action in scaffold generator.
+
+ *Rahul P. Chaudhari*
+
+* Add two new test rake tasks to speed up full test runs.
+
+ * `test:all`: run tests quickly by merging all types and not resetting db.
+ * `test:all:db`: run tests quickly, but also reset db.
+
+ *Ryan Davis*
+
+* Add `--rc` option to support the load of a custom rc file during the generation of a new app.
*Amparo Luna*
-* Add --no-rc option to skip the loading of railsrc file during the generation of a new app.
-
+* Add `--no-rc` option to skip the loading of railsrc file during the generation of a new app.
+
*Amparo Luna*
* Fixes database.yml when creating a new rails application with '.'
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 7d84d3ae0e..2ff29418c6 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -10,8 +10,12 @@ if ARGV.first != "new"
else
ARGV.shift
unless ARGV.delete("--no-rc")
- customrc = ARGV.index('--rc')
- railsrc = customrc ? ARGV.slice!(customrc, 2).last : File.join(File.expand_path("~"), '.railsrc')
+ customrc = ARGV.index{ |x| x.include?("--rc=") }
+ railsrc = if customrc
+ File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, ""))
+ else
+ File.join(File.expand_path("~"), '.railsrc')
+ end
if File.exist?(railsrc)
extra_args_string = File.read(railsrc)
extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index ca3652c703..99d80b3245 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -20,10 +20,10 @@ module Rails
def self.add_shared_options_for(name)
class_option :builder, type: :string, aliases: '-b',
- desc: "Path to a #{name} builder (can be a filesystem path or URL)"
+ desc: "Path to some #{name} builder (can be a filesystem path or URL)"
class_option :template, type: :string, aliases: '-m',
- desc: "Path to an #{name} template (can be a filesystem path or URL)"
+ desc: "Path to some #{name} template (can be a filesystem path or URL)"
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
@@ -61,6 +61,12 @@ module Rails
class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false,
desc: 'Skip Test::Unit files'
+ class_option :rc, type: :string, default: false,
+ desc: "Path to file containing extra configuration options for rails command"
+
+ class_option :no_rc, type: :boolean, default: false,
+ desc: 'Skip loading of extra configuration options from .railsrc file'
+
class_option :help, type: :boolean, aliases: '-h', group: :rails,
desc: 'Show this help message and quit'
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 5bf98bb6e0..cd69a017dd 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -52,7 +52,7 @@ module Rails
if destination && options.force?
remove_file(destination)
elsif destination
- raise Error, "Another migration is already named #{@migration_file_name}: #{destination}"
+ raise Error, "Another migration is already named #{@migration_file_name}: #{destination}. Use --force to remove the old migration file and replace it."
end
destination = File.join(migration_dir, "#{@migration_number}_#{@migration_file_name}.rb")
end
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index e813437d75..72281a2fef 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -47,7 +47,7 @@ class <%= controller_class_name %>Controller < ApplicationController
# DELETE <%= route_url %>/1
def destroy
@<%= orm_instance.destroy %>
- redirect_to <%= index_helper %>_url
+ redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %>
end
private
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index 4536fedaa3..3b7f358a5b 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -2,12 +2,12 @@ if RUBY_VERSION < '1.9.3'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 4 requires Ruby 1.9.3+.
+ Rails 4 prefers to run on Ruby 2.0.
You're running
#{desc}
- Please upgrade to continue.
+ Please upgrade to Ruby 1.9.3 or newer to continue.
end_message
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index f0d46fd959..44485d9b14 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -71,6 +71,18 @@ namespace :test do
end
end
+ # Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html
+ desc "Run tests quickly by merging all types and not resetting db"
+ Rake::TestTask.new(:all) do |t|
+ t.libs << "test"
+ t.pattern = "test/**/*_test.rb"
+ end
+
+ namespace :all do
+ desc "Run tests quickly, but also reset db"
+ task :db => %w[db:test:prepare test:all]
+ end
+
Rake::TestTask.new(recent: "test:prepare") do |t|
since = TEST_CHANGES_SINCE
touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } +
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index c34ce285e3..013cb78252 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -39,6 +39,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_instance_method :destroy, content do |m|
assert_match(/@user\.destroy/, m)
+ assert_match(/User was successfully destroyed/, m)
end
assert_instance_method :set_user, content do |m|