aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md10
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_view.rb4
-rw-r--r--actionpack/lib/action_view/asset_paths.rb8
-rw-r--r--actionpack/test/controller/routing_test.rb5
-rw-r--r--actionpack/test/dispatch/routing_test.rb4
-rw-r--r--actionpack/test/fixtures/public/foo/baz.css3
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb14
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb3
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb10
-rw-r--r--activemodel/lib/active_model/serializers/json.rb5
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/test/cases/configuration_test.rb154
-rw-r--r--activerecord/lib/active_record.rb10
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb19
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb9
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb13
-rw-r--r--activerecord/lib/active_record/callbacks.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_handling.rb4
-rw-r--r--activerecord/lib/active_record/core.rb137
-rw-r--r--activerecord/lib/active_record/counter_cache.rb200
-rw-r--r--activerecord/lib/active_record/explain.rb11
-rw-r--r--activerecord/lib/active_record/inheritance.rb10
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/model.rb104
-rw-r--r--activerecord/lib/active_record/model_schema.rb23
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/railtie.rb26
-rw-r--r--activerecord/lib/active_record/railties/databases.rake192
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb3
-rw-r--r--activerecord/lib/active_record/scoping/default.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb12
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb87
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb93
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb50
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb41
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb4
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb1
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/database_tasks_test.rb297
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb59
-rw-r--r--activerecord/test/cases/mysql_rake_test.rb179
-rw-r--r--activerecord/test/cases/postgresql_rake_test.rb135
-rw-r--r--activerecord/test/cases/sqlite_rake_test.rb106
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb2
-rw-r--r--guides/source/routing.textile8
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile2
-rw-r--r--railties/test/application/configuration_test.rb39
-rw-r--r--railties/test/application/rake_test.rb12
68 files changed, 1601 insertions, 803 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 8c2d887cb3..345b5aa330 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,15 @@
## Rails 4.0.0 (unreleased) ##
+* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
+
+ get Rack::Utils.escape('こんにちは') => 'home#index'
+
+ You just have to write the unicode route:
+
+ get 'こんにちは' => 'home#index'
+
+ *kennyj*
+
* Return proper format on exceptions. *Santiago Pastorino*
* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index ae26d6f9e5..938f3d6dc2 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency('journey', '~> 1.0.1')
s.add_dependency('erubis', '~> 2.7.0')
- s.add_development_dependency('tzinfo', '~> 0.3.29')
+ s.add_development_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 204f3f7fb3..53bedaa40a 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,5 +1,4 @@
module ActionDispatch
- # A simple Rack application that renders exceptions in the given public path.
class PublicExceptions
attr_accessor :public_path
@@ -12,26 +11,24 @@ module ActionDispatch
status = env["PATH_INFO"][1..-1]
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
- format = content_type && "to_#{content_type.to_sym}"
body = { :status => status, :error => exception.message }
- render(status, body, :format => format, :content_type => content_type)
+ render(status, content_type, body)
end
private
- def render(status, body, options)
- format = options[:format]
-
+ def render(status, content_type, body)
+ format = content_type && "to_#{content_type.to_sym}"
if format && body.respond_to?(format)
- render_format(status, body.public_send(format), options)
+ render_format(status, content_type, body.public_send(format))
else
render_html(status)
end
end
- def render_format(status, body, options)
- [status, {'Content-Type' => "#{options[:content_type]}; charset=#{ActionDispatch::Response.default_charset}",
+ def render_format(status, content_type, body)
+ [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [body]]
end
@@ -41,8 +38,7 @@ module ActionDispatch
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
if found || File.exist?(path)
- body = File.read(path)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ render_format(status, 'text/html', File.read(path))
else
[404, { "X-Cascade" => "pass" }, []]
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 38a0270151..29090882a5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
@@ -218,6 +219,12 @@ module ActionDispatch
#
# match "/stories" => redirect("/posts")
#
+ # == Unicode character routes
+ #
+ # You can specify unicode character routes in your router:
+ #
+ # match "こんにちは" => "welcome#index"
+ #
# == Routing to Rack Applications
#
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 25d099d83e..94242ad962 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1387,7 +1387,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@set, @scope, path, options)
+ mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 3823f87027..03dfa110e3 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -36,7 +36,7 @@ module ActionView
autoload :LookupContext
autoload :PathSet
autoload :Template
- autoload :TestCase
+
autoload_under "renderer" do
autoload :Renderer
@@ -73,6 +73,8 @@ module ActionView
end
end
+ autoload :TestCase
+
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 4ce41d51f1..81880d17ea 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -35,7 +35,13 @@ module ActionView
# Return the filesystem path for the source
def compute_source_path(source, dir, ext)
source = rewrite_extension(source, dir, ext) if ext
- File.join(config.assets_dir, dir, source)
+
+ sources = []
+ sources << config.assets_dir
+ sources << dir unless source[0] == ?/
+ sources << source
+
+ File.join(sources)
end
def is_uri?(path)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 306b8a93da..6cc1370105 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1760,6 +1760,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
get 'account(/:action)' => "account#subscription"
get 'pages/:page_id/:controller(/:action(/:id))'
get ':controller/ping', :action => 'ping'
+ get 'こんにちは/世界', :controller => 'news', :action => 'index'
match ':controller(/:action(/:id))(.:format)', :via => :all
root :to => "news#index"
}
@@ -1876,6 +1877,10 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params)
end
+ def test_unicode_path
+ assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界'), :method => :get))
+ end
+
private
def sort_extras!(extras)
if extras.length == 2
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index fa4cb301eb..f15dd7214b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2472,7 +2472,7 @@ end
class TestUnicodePaths < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
- get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
+ get "/ほげ" => lambda { |env|
[200, { 'Content-Type' => 'text/plain' }, []]
}, :as => :unicode_path
end
@@ -2727,4 +2727,4 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
assert_response :bad_request
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/fixtures/public/foo/baz.css b/actionpack/test/fixtures/public/foo/baz.css
new file mode 100644
index 0000000000..b5173fbef2
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/baz.css
@@ -0,0 +1,3 @@
+body {
+background: #000;
+}
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 7cc567c72d..bcc55189b9 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -1267,9 +1267,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
-
-
-
def test_caching_stylesheet_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
config.perform_caching = false
@@ -1298,6 +1295,17 @@ class AssetTagHelperTest < ActionView::TestCase
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
+
+ def test_caching_stylesheet_include_tag_with_absolute_uri
+ ENV["RAILS_ASSET_ID"] = ""
+
+ assert_dom_equal(
+ %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag("/foo/baz", :cache => true)
+ )
+
+ FileUtils.rm(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+ end
end
class AssetTagHelperNonVhostTest < ActionView::TestCase
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ded1b752df..f6bacf5ec0 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,7 +30,6 @@ module ActiveModel
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
- autoload :Configuration
autoload :Conversion
autoload :Dirty
autoload :EachValidator, 'active_model/validator'
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 846d0d7f86..b5b50847c2 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -61,8 +61,7 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- extend ActiveModel::Configuration
- config_attribute :attribute_method_matchers
+ class_attribute :attribute_method_matchers, instance_writer: false
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
deleted file mode 100644
index ba5a6a2075..0000000000
--- a/activemodel/lib/active_model/configuration.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'active_support/concern'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module ActiveModel
- # This API is for Rails' internal use and is not currently considered 'public', so
- # it may change in the future without warning.
- #
- # It creates configuration attributes that can be inherited from a module down
- # to a class that includes the module. E.g.
- #
- # module MyModel
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- #
- # class Post
- # include MyModel
- # end
- #
- # Post.awesome # => true
- #
- # Post.awesome = false
- # Post.awesome # => false
- # MyModel.awesome # => true
- #
- # We assume that the module will have a ClassMethods submodule containing methods
- # to be transferred to the including class' singleton class.
- #
- # Config options can also be defined directly on a class:
- #
- # class Post
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # end
- #
- # So this allows us to define a module that doesn't care about whether it is being
- # included in a class or a module:
- #
- # module Awesomeness
- # extend ActiveSupport::Concern
- #
- # included do
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- # end
- #
- # class Post
- # include Awesomeness
- # end
- #
- # module AwesomeModel
- # include Awesomeness
- # end
- module Configuration #:nodoc:
- def config_attribute(name, options = {})
- klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
- klass.new(self, name, options).define
- end
-
- class Attribute
- attr_reader :host, :name, :options
-
- def initialize(host, name, options)
- @host, @name, @options = host, name, options
- end
-
- def instance_writer?
- options.fetch(:instance_writer, false)
- end
- end
-
- class ClassAttribute < Attribute
- def define
- if options[:global]
- host.cattr_accessor name, :instance_writer => instance_writer?
- else
- host.class_attribute name, :instance_writer => instance_writer?
- end
- end
- end
-
- class ModuleAttribute < Attribute
- def class_methods
- @class_methods ||= begin
- if host.const_defined?(:ClassMethods, false)
- host.const_get(:ClassMethods)
- else
- host.const_set(:ClassMethods, Module.new)
- end
- end
- end
-
- def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- attr_accessor :#{name}
- def #{name}?; !!#{name}; end
- CODE
-
- name, host = self.name, self.host
-
- class_methods.class_eval do
- define_method(name) { host.send(name) }
- define_method("#{name}?") { !!send(name) }
- end
-
- host.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
- def #{name}?; !!#{name}; end
- CODE
-
- if options[:global]
- class_methods.class_eval do
- define_method("#{name}=") { |val| host.send("#{name}=", val) }
- end
- else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
- end
- CODE
- end
-
- host.send(:attr_writer, name) if instance_writer?
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index cfce1542b1..8f2c0bf00a 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -9,13 +9,11 @@ module ActiveModel
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
+ class_attribute :_accessible_attributes, instance_writer: false
+ class_attribute :_protected_attributes, instance_writer: false
+ class_attribute :_active_authorizer, instance_writer: false
- config_attribute :_accessible_attributes
- config_attribute :_protected_attributes
- config_attribute :_active_authorizer
-
- config_attribute :_mass_assignment_sanitizer
+ class_attribute :_mass_assignment_sanitizer, instance_writer: false
self.mass_assignment_sanitizer = :logger
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index e668b4a009..b4baf3a946 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,9 +10,8 @@ module ActiveModel
included do
extend ActiveModel::Naming
- extend ActiveModel::Configuration
- config_attribute :include_root_in_json
+ class_attribute :include_root_in_json
self.include_root_in_json = false
end
@@ -106,4 +105,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 611e9ffd55..06eebf79d9 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -52,8 +52,7 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- extend ActiveModel::Configuration
- config_attribute :_validators
+ class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb
deleted file mode 100644
index a172fa26a3..0000000000
--- a/activemodel/test/cases/configuration_test.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-require 'cases/helper'
-
-class ConfigurationOnModuleTest < ActiveModel::TestCase
- def setup
- @mod = mod = Module.new do
- extend ActiveSupport::Concern
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :boolean
-
- config_attribute :lol, instance_writer: true
- end
-
- @klass = Class.new do
- include mod
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "default" do
- assert_equal "default", @mod.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.new.omg
- end
-
- test "setting" do
- @mod.omg = "lol"
- assert_equal "lol", @mod.omg
- end
-
- test "setting on class including the module" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @klass.new.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on subclass of class including the module" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on instance" do
- assert !@klass.new.respond_to?(:omg=)
-
- @klass.lol = "lol"
- obj = @klass.new
- assert_equal "lol", obj.lol
- obj.lol = "omg"
- assert_equal "omg", obj.lol
- assert_equal "lol", @klass.lol
- assert_equal "lol", @klass.new.lol
- obj.lol = false
- assert !obj.lol?
- end
-
- test "global attribute" do
- assert_equal "default", @mod.wtf
- assert_equal "default", @klass.wtf
-
- @mod.wtf = "wtf"
-
- assert_equal "wtf", @mod.wtf
- assert_equal "wtf", @klass.wtf
-
- @klass.wtf = "lol"
-
- assert_equal "lol", @mod.wtf
- assert_equal "lol", @klass.wtf
- end
-
- test "boolean" do
- assert_equal false, @mod.boolean?
- assert_equal false, @klass.new.boolean?
- @mod.boolean = true
- assert_equal true, @mod.boolean?
- assert_equal true, @klass.new.boolean?
- end
-end
-
-class ConfigurationOnClassTest < ActiveModel::TestCase
- def setup
- @klass = Class.new do
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :omg2, instance_writer: true
- config_attribute :wtf2, instance_writer: true, global: true
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "defaults" do
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.wtf
- assert_equal "default", @subklass.omg
- assert_equal "default", @subklass.wtf
- end
-
- test "changing" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @subklass.omg
- end
-
- test "changing in subclass" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- end
-
- test "changing global" do
- @klass.wtf = "wtf"
- assert_equal "wtf", @klass.wtf
- assert_equal "wtf", @subklass.wtf
-
- @subklass.wtf = "lol"
- assert_equal "lol", @klass.wtf
- assert_equal "lol", @subklass.wtf
- end
-
- test "instance_writer" do
- obj = @klass.new
-
- @klass.omg2 = "omg"
- @klass.wtf2 = "wtf"
-
- assert_equal "omg", obj.omg2
- assert_equal "wtf", obj.wtf2
-
- obj.omg2 = "lol"
- obj.wtf2 = "lol"
-
- assert_equal "lol", obj.omg2
- assert_equal "lol", obj.wtf2
- assert_equal "omg", @klass.omg2
- assert_equal "lol", @klass.wtf2
- end
-end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index f8526bb691..8526e224da 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -137,6 +137,16 @@ module ActiveRecord
end
end
+ module Tasks
+ extend ActiveSupport::Autoload
+
+ autoload :DatabaseTasks
+ autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
+ autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
+ autoload :PostgreSQLDatabaseTasks,
+ 'active_record/tasks/postgresql_database_tasks'
+ end
+
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index abc2fa546a..df4de8ac35 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -1,11 +1,25 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :whitelist_attributes, instance_accessor: false
+ mattr_accessor :mass_assignment_sanitizer, instance_accessor: false
+ end
+
module AttributeAssignment
extend ActiveSupport::Concern
include ActiveModel::MassAssignmentSecurity
+ included do
+ initialize_mass_assignment_sanitizer
+ end
+
module ClassMethods
+ def inherited(child) # :nodoc:
+ child.send :initialize_mass_assignment_sanitizer if self == Base
+ super
+ end
+
private
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
@@ -14,6 +28,11 @@ module ActiveRecord
default << 'id' unless primary_key.eql? 'id'
default
end
+
+ def initialize_mass_assignment_sanitizer
+ attr_accessible(nil) if Model.whitelist_attributes
+ self.mass_assignment_sanitizer = Model.mass_assignment_sanitizer if Model.mass_assignment_sanitizer
+ end
end
# Allows you to set all the attributes at once by passing in a hash with keys
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 172026d150..f36df4a444 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -16,19 +16,6 @@ module ActiveRecord
include TimeZoneConversion
include Dirty
include Serialization
-
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- # (Alias for the protected read_attribute method).
- def [](attr_name)
- read_attribute(attr_name)
- end
-
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
- # (Alias for the protected write_attribute method).
- def []=(attr_name, value)
- write_attribute(attr_name, value)
- end
end
module ClassMethods
@@ -192,6 +179,19 @@ module ActiveRecord
self.class.columns_hash[name.to_s]
end
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ # (Alias for the protected read_attribute method).
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
+
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
+ # (Alias for the protected write_attribute method).
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
+
protected
def clone_attributes(reader_method = :read_attribute, attributes = {})
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 11c63591e3..f8a40ad520 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,12 +1,18 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :partial_updates, instance_accessor: false
+ self.partial_updates = true
+ end
+
module AttributeMethods
module Dirty
extend ActiveSupport::Concern
+
include ActiveModel::Dirty
- include AttributeMethods::Write
included do
if self < ::ActiveRecord::Timestamp
@@ -14,7 +20,6 @@ module ActiveRecord
end
config_attribute :partial_updates
- self.partial_updates = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index dcc3d79de9..a7af086e43 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,13 +1,17 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :attribute_types_cached_by_default, instance_accessor: false
+ end
+
module AttributeMethods
module Read
extend ActiveSupport::Concern
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+ ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
included do
- config_attribute :attribute_types_cached_by_default, :global => true
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ config_attribute :attribute_types_cached_by_default
end
module ClassMethods
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 706fbf0546..4af4d28b74 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -6,7 +6,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.
- config_attribute :serialized_attributes
+ class_attribute :serialized_attributes
self.serialized_attributes = {}
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 58a5d82e14..e300c9721f 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,6 +2,14 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/inclusion'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :time_zone_aware_attributes, instance_accessor: false
+ self.time_zone_aware_attributes = false
+
+ mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false
+ self.skip_time_zone_conversion_for_attributes = []
+ end
+
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
@@ -22,11 +30,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :time_zone_aware_attributes, :global => true
- self.time_zone_aware_attributes = false
-
+ config_attribute :time_zone_aware_attributes, global: true
config_attribute :skip_time_zone_conversion_for_attributes
- self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index a050fabf35..cc11567506 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -231,30 +231,6 @@ module ActiveRecord
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
#
module Callbacks
- # We can't define callbacks directly on ActiveRecord::Model because
- # it is a module. So we queue up the definitions and execute them
- # when ActiveRecord::Model is included.
- module Register #:nodoc:
- def self.extended(base)
- base.config_attribute :_callbacks_register
- base._callbacks_register = []
- end
-
- def self.setup(base)
- base._callbacks_register.each do |item|
- base.send(*item)
- end
- end
-
- def define_callbacks(*args)
- self._callbacks_register << [:define_callbacks, *args]
- end
-
- def define_model_callbacks(*args)
- self._callbacks_register << [:define_model_callbacks, *args]
- end
- end
-
extend ActiveSupport::Concern
CALLBACKS = [
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 61ff603006..347d794fa3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -448,11 +448,11 @@ module ActiveRecord
end
def new_connection
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ ActiveRecord::Model.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
- ActiveRecord::Base.connection_id ||= Thread.current.object_id
+ ActiveRecord::Model.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
@@ -563,10 +563,12 @@ module ActiveRecord
end
def retrieve_connection_pool(klass)
- pool = get_pool_for_class klass.name
- return pool if pool
- return nil if ActiveRecord::Model == klass
- retrieve_connection_pool klass.active_record_super
+ if !(klass < Model::Tag)
+ get_pool_for_class('ActiveRecord::Model') # default connection
+ else
+ pool = get_pool_for_class(klass.name)
+ pool || retrieve_connection_pool(klass.superclass)
+ end
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 4c6d03a1d2..b0b51f540c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -370,7 +370,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.rolledback!(rollback)
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
@@ -385,7 +385,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.committed!
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c6faae77cc..28a9821913 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -286,7 +286,7 @@ module ActiveRecord
:name => name,
:connection_id => object_id,
:binds => binds) { yield }
- rescue Exception => e
+ rescue => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
exception = translate_exception(e, message)
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 692473abc5..921278d145 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -264,19 +264,19 @@ module ActiveRecord
def begin_db_transaction
execute "BEGIN"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def commit_db_transaction #:nodoc:
execute "COMMIT"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def rollback_db_transaction #:nodoc:
execute "ROLLBACK"
- rescue Exception
+ rescue
# Transactions aren't supported
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 7b218a5570..bda41df80f 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -90,6 +90,10 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
+ def clear_cache! # :nodoc:
+ connection.schema_cache.clear!
+ end
+
delegate :clear_active_connections!, :clear_reloadable_connections!,
:clear_all_connections!, :to => :connection_handler
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index dbad561ca2..f5d60d11b6 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,80 +1,88 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/module/delegation'
require 'thread'
module ActiveRecord
- module Core
- extend ActiveSupport::Concern
+ ActiveSupport.on_load(:active_record_config) do
+ ##
+ # :singleton-method:
+ #
+ # Accepts a logger conforming to the interface of Log4r which is then
+ # passed on to any new database connections made and which can be
+ # retrieved on both a class and instance level by calling +logger+.
+ mattr_accessor :logger, instance_accessor: false
- included do
- ##
- # :singleton-method:
- #
- # Accepts a logger conforming to the interface of Log4r which is then
- # passed on to any new database connections made and which can be
- # retrieved on both a class and instance level by calling +logger+.
- config_attribute :logger, :global => true
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ mattr_accessor :configurations, instance_accessor: false
+ self.configurations = {}
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- config_attribute :configurations, :global => true
- self.configurations = {}
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ mattr_accessor :default_timezone, instance_accessor: false
+ self.default_timezone = :utc
- ##
- # :singleton-method:
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
- # dates and times from the database. This is set to :utc by default.
- config_attribute :default_timezone, :global => true
- self.default_timezone = :utc
+ ##
+ # :singleton-method:
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ mattr_accessor :schema_format, instance_accessor: false
+ self.schema_format = :ruby
- ##
- # :singleton-method:
- # Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- config_attribute :schema_format, :global => true
- self.schema_format = :ruby
+ ##
+ # :singleton-method:
+ # Specify whether or not to use timestamps for migration versions
+ mattr_accessor :timestamped_migrations, instance_accessor: false
+ self.timestamped_migrations = true
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- config_attribute :timestamped_migrations, :global => true
- self.timestamped_migrations = true
+ mattr_accessor :connection_handler, instance_accessor: false
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ mattr_accessor :dependent_restrict_raises, instance_accessor: false
+ self.dependent_restrict_raises = true
+ end
+ module Core
+ extend ActiveSupport::Concern
+
+ included do
##
# :singleton-method:
# The connection handler
config_attribute :connection_handler
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
##
# :singleton-method:
@@ -83,8 +91,11 @@ module ActiveRecord
# ActiveRecord::DeleteRestrictionError exception will be raised
# along with a DEPRECATION WARNING. If set to false, an error would
# be added to the model instead.
- config_attribute :dependent_restrict_raises, :global => true
- self.dependent_restrict_raises = true
+ config_attribute :dependent_restrict_raises
+
+ %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
+ config_attribute name, global: true
+ end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index b163ef3c12..b27a19f89a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -1,111 +1,115 @@
module ActiveRecord
# = Active Record Counter Cache
module CounterCache
- # Resets one or more counter caches to their correct value using an SQL
- # count query. This is useful when adding new counter caches, or if the
- # counter has been corrupted or modified directly by SQL.
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
- #
- # ==== Examples
- #
- # # For Post with id #1 records reset the comments_count
- # Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
- object = find(id)
- counters.each do |association|
- has_many_association = reflect_on_association(association.to_sym)
+ extend ActiveSupport::Concern
- foreign_key = has_many_association.foreign_key.to_s
- child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
- counter_name = reflection.counter_cache_column
+ module ClassMethods
+ # Resets one or more counter caches to their correct value using an SQL
+ # count query. This is useful when adding new counter caches, or if the
+ # counter has been corrupted or modified directly by SQL.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to reset a counter on.
+ # * +counters+ - One or more counter names to reset
+ #
+ # ==== Examples
+ #
+ # # For Post with id #1 records reset the comments_count
+ # Post.reset_counters(1, :comments)
+ def reset_counters(id, *counters)
+ object = find(id)
+ counters.each do |association|
+ has_many_association = reflect_on_association(association.to_sym)
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(association).count
- })
- connection.update stmt
- end
- return true
- end
+ foreign_key = has_many_association.foreign_key.to_s
+ child_class = has_many_association.klass
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
+ counter_name = reflection.counter_cache_column
- # A generic "counter updater" implementation, intended primarily to be
- # used by increment_counter and decrement_counter, but which may also
- # be useful on its own. It simply does a direct SQL update for the record
- # with the given ID, altering the given hash of counters by the amount
- # given by the corresponding value:
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
- # * +counters+ - An Array of Hashes containing the names of the fields
- # to update as keys and the amount to update the field by as values.
- #
- # ==== Examples
- #
- # # For the Post with id of 5, decrement the comment_count by 1, and
- # # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
- # # action_count = COALESCE(action_count, 0) + 1
- # # WHERE id = 5
- #
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
- # Post.update_counters [10, 15], :comment_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) + 1
- # # WHERE id IN (10, 15)
- def update_counters(id, counters)
- updates = counters.map do |counter_name, value|
- operator = value < 0 ? '-' : '+'
- quoted_column = connection.quote_column_name(counter_name)
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
+ arel_table[counter_name] => object.send(association).count
+ })
+ connection.update stmt
+ end
+ return true
end
- where(primary_key => id).update_all updates.join(', ')
- end
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
+ # * +counters+ - An Array of Hashes containing the names of the fields
+ # to update as keys and the amount to update the field by as values.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
+ # # action_count = COALESCE(action_count, 0) + 1
+ # # WHERE id = 5
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # Post.update_counters [10, 15], :comment_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
+ # # WHERE id IN (10, 15)
+ def update_counters(id, counters)
+ updates = counters.map do |counter_name, value|
+ operator = value < 0 ? '-' : '+'
+ quoted_column = connection.quote_column_name(counter_name)
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ end
- # Increment a number field by one, usually representing a count.
- #
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
- #
- # ==== Examples
- #
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
- end
+ where(primary_key => id).update_all updates.join(', ')
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
- # Decrement a number field by one, usually representing a count.
- #
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
- #
- # ==== Examples
- #
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index b0eda8ef34..7ade385c70 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,12 +1,13 @@
+require 'active_support/lazy_load_hooks'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
+ end
+
module Explain
- def self.extended(base)
- # If a query takes longer than these many seconds we log its query plan
- # automatically. nil disables this feature.
- base.config_attribute :auto_explain_threshold_in_seconds, :global => true
- end
+ delegate :auto_explain_threshold_in_seconds, :auto_explain_threshold_in_seconds=, to: 'ActiveRecord::Model'
# If auto explain is enabled, this method triggers EXPLAIN logging for the
# queries triggered by the block if it takes more than the threshold as a
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 46d253b0a7..770083ac13 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -1,13 +1,17 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ # Determine whether to store the full constant name including namespace when using STI
+ mattr_accessor :store_full_sti_class, instance_accessor: false
+ self.store_full_sti_class = true
+ end
+
module Inheritance
extend ActiveSupport::Concern
included do
- # Determine whether to store the full constant name including namespace when using STI
config_attribute :store_full_sti_class
- self.store_full_sti_class = true
end
module ClassMethods
@@ -95,7 +99,7 @@ module ActiveRecord
# Returns the class descending directly from ActiveRecord::Base or an
# abstract class, if any, in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
- unless klass < Model
+ unless klass < Model::Tag
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 05e052b953..4ce42feb74 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -1,4 +1,9 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :lock_optimistically, instance_accessor: false
+ self.lock_optimistically = true
+ end
+
module Locking
# == What is Optimistic Locking
#
@@ -51,8 +56,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :lock_optimistically, :global => true
- self.lock_optimistically = true
+ config_attribute :lock_optimistically
end
def locking_enabled? #:nodoc:
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 105d1e0e2b..d51cb30ec1 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -1,6 +1,34 @@
require 'active_support/deprecation'
+require 'active_support/concern'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ module Configuration # :nodoc:
+ # This just abstracts out how we define configuration options in AR. Essentially we
+ # have mattr_accessors on the ActiveRecord:Model constant that define global defaults.
+ # Classes that then use AR get class_attributes defined, which means that when they
+ # are assigned the default will be overridden for that class and subclasses. (Except
+ # when options[:global] == true, in which case there is one global value always.)
+ def config_attribute(name, options = {})
+ if options[:global]
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}; ActiveRecord::Model.#{name}; end
+ def #{name}; ActiveRecord::Model.#{name}; end
+ def self.#{name}=(val); ActiveRecord::Model.#{name} = val; end
+ CODE
+ else
+ options[:instance_writer] ||= false
+ class_attribute name, options
+
+ singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ remove_method :#{name}
+ def #{name}; ActiveRecord::Model.#{name}; end
+ CODE
+ end
+ end
+ end
+
# <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence.
# This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example:
#
@@ -9,41 +37,35 @@ module ActiveRecord
# end
#
module Model
- module ClassMethods #:nodoc:
- include ActiveSupport::Callbacks::ClassMethods
- include ActiveModel::Naming
- include QueryCache::ClassMethods
- include ActiveSupport::Benchmarkable
- include ActiveSupport::DescendantsTracker
-
- include Querying
- include Translation
- include DynamicMatchers
- include CounterCache
- include Explain
- include ConnectionHandling
- end
+ extend ActiveSupport::Concern
+ extend ConnectionHandling
+ extend ActiveModel::Observing::ClassMethods
- def self.included(base)
- return if base.singleton_class < ClassMethods
+ # This allows us to detect an ActiveRecord::Model while it's in the process of being included.
+ module Tag; end
+ def self.append_features(base)
base.class_eval do
- extend ClassMethods
- Callbacks::Register.setup(self)
- initialize_generated_modules unless self == Base
+ include Tag
+ extend Configuration
end
+
+ super
end
- extend ActiveModel::Configuration
- extend ActiveModel::Callbacks
- extend ActiveModel::MassAssignmentSecurity::ClassMethods
- extend ActiveModel::AttributeMethods::ClassMethods
- extend Callbacks::Register
- extend Explain
- extend ConnectionHandling
+ included do
+ extend ActiveModel::Naming
+ extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
+
+ extend QueryCache::ClassMethods
+ extend Querying
+ extend Translation
+ extend DynamicMatchers
+ extend Explain
+ extend ConnectionHandling
- def self.extend(*modules)
- ClassMethods.send(:include, *modules)
+ initialize_generated_modules unless self == Base
end
include Persistence
@@ -56,13 +78,22 @@ module ActiveRecord
include AttributeAssignment
include ActiveModel::Conversion
include Validations
- include Locking::Optimistic, Locking::Pessimistic
+ include CounterCache
+ include Locking::Optimistic
+ include Locking::Pessimistic
include AttributeMethods
- include Callbacks, ActiveModel::Observing, Timestamp
+ include Callbacks
+ include ActiveModel::Observing
+ include Timestamp
include Associations
include ActiveModel::SecurePassword
- include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Serialization, Store
+ include AutosaveAssociation
+ include NestedAttributes
+ include Aggregations
+ include Transactions
+ include Reflection
+ include Serialization
+ include Store
include Core
class << self
@@ -103,6 +134,15 @@ module ActiveRecord
end
end
+ # This hook is where config accessors on Model get defined.
+ #
+ # We don't want to just open the Model module and add stuff to it in other files, because
+ # that would cause Model to load, which causes all sorts of loading order issues.
+ #
+ # We need this hook rather than just using the :active_record one, because users of the
+ # :active_record hook may need to use config options.
+ ActiveSupport.run_load_hooks(:active_record_config, Model)
+
# Load Base at this point, because the active_record load hook is run in that file.
Base
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 7f38dda11e..e6b76ddc4c 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,7 +1,19 @@
require 'active_support/concern'
-require 'active_support/core_ext/class/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :primary_key_prefix_type, instance_accessor: false
+
+ mattr_accessor :table_name_prefix, instance_accessor: false
+ self.table_name_prefix = ""
+
+ mattr_accessor :table_name_suffix, instance_accessor: false
+ self.table_name_suffix = ""
+
+ mattr_accessor :pluralize_table_names, instance_accessor: false
+ self.pluralize_table_names = true
+ end
+
module ModelSchema
extend ActiveSupport::Concern
@@ -13,7 +25,7 @@ module ActiveRecord
# the Product class will look for "productid" instead of "id" as the primary column. If the
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
# that this is a global setting for all Active Records.
- config_attribute :primary_key_prefix_type, :global => true
+ config_attribute :primary_key_prefix_type, global: true
##
# :singleton-method:
@@ -26,14 +38,12 @@ module ActiveRecord
# a namespace by defining a singleton method in the parent module called table_name_prefix which
# returns your chosen prefix.
config_attribute :table_name_prefix
- self.table_name_prefix = ""
##
# :singleton-method:
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
# "people_basecamp"). By default, the suffix is the empty string.
config_attribute :table_name_suffix
- self.table_name_suffix = ""
##
# :singleton-method:
@@ -41,7 +51,6 @@ module ActiveRecord
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
config_attribute :pluralize_table_names
- self.pluralize_table_names = true
end
module ClassMethods
@@ -308,10 +317,6 @@ module ActiveRecord
@relation = nil
end
- def clear_cache! # :nodoc:
- connection.schema_cache.clear!
- end
-
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 95a2ddcc11..841681e542 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -5,6 +5,11 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :nested_attributes_options, instance_accessor: false
+ self.nested_attributes_options = {}
+ end
+
module NestedAttributes #:nodoc:
class TooManyRecords < ActiveRecordError
end
@@ -13,7 +18,6 @@ module ActiveRecord
included do
config_attribute :nested_attributes_options
- self.nested_attributes_options = {}
end
# = Active Record Nested Attributes
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 319516413b..9432a70c41 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -68,9 +68,6 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
- if app.config.active_record.delete(:whitelist_attributes)
- attr_accessible(nil)
- end
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -97,18 +94,12 @@ module ActiveRecord
end
initializer "active_record.set_reloader_hooks" do |app|
- hook = lambda do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
- end
+ hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
- if app.config.reload_classes_only_on_change
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_prepare(&hook)
- end
- else
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_cleanup(&hook)
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.send(hook) do
+ ActiveRecord::Model.clear_reloadable_connections!
+ ActiveRecord::Model.clear_cache!
end
end
end
@@ -119,20 +110,21 @@ module ActiveRecord
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
ActionDispatch::Reloader.to_prepare do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
end
end
ActiveSupport.on_load(:active_record) do
if app.config.use_schema_cache_dump
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+
if File.file?(filename)
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
- ActiveRecord::Base.connection.schema_cache = cache
+ ActiveRecord::Model.connection.schema_cache = cache
else
warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d8d4834d22..539836e9ed 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -14,138 +14,27 @@ db_namespace = namespace :db do
end
namespace :create do
- # desc 'Create all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key, such as the first entry here:
- #
- # defaults: &defaults
- # adapter: mysql
- # username: root
- # password:
- # host: localhost
- #
- # development:
- # database: blog_development
- # *defaults
- next unless config['database']
- # Only connect to local databases
- local_database?(config) { create_database(config) }
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
task :create => :load_config do
- configs_for_environment.each { |config| create_database(config) }
- ActiveRecord::Base.establish_connection(configs_for_environment.first)
- end
-
- def mysql_creation_options(config)
- @charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
- {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
- end
-
- def create_database(config)
- begin
- if config['adapter'] =~ /sqlite/
- if File.exist?(config['database'])
- $stderr.puts "#{config['database']} already exists"
- else
- begin
- # Create the SQLite database
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- return # Skip the else clause of begin/rescue
- else
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- end
- rescue
- case config['adapter']
- when /mysql/
- if config['adapter'] =~ /jdbc/
- #FIXME After Jdbcmysql gives this class
- require 'active_record/railties/jdbcmysql_error'
- error_class = ArJdbcMySQL::Error
- else
- error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
- end
- access_denied_error = 1045
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.establish_connection(config)
- rescue error_class => sqlerr
- if sqlerr.errno == access_denied_error
- print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
- root_password = $stdin.gets.strip
- grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
- "TO '#{config['username']}'@'localhost' " \
- "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
- ActiveRecord::Base.establish_connection(config.merge(
- 'database' => nil, 'username' => 'root', 'password' => root_password))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.connection.execute grant_statement
- ActiveRecord::Base.establish_connection(config)
- else
- $stderr.puts sqlerr.error
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}"
- $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
- end
- end
- when /postgresql/
- @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
- ActiveRecord::Base.establish_connection(config)
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- else
- $stderr.puts "#{config['database']} already exists"
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_current
end
namespace :drop do
- # desc 'Drops all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key
- next unless config['database']
- begin
- # Only connect to local databases
- local_database?(config) { drop_database(config) }
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
task :drop => :load_config do
- configs_for_environment.each { |config| drop_database_and_rescue(config) }
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
end
- def local_database?(config, &block)
- if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank?
- yield
- else
- $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
- end
- end
-
-
desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
@@ -247,13 +136,13 @@ db_namespace = namespace :db do
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
- task :reset => :environment do
+ task :reset => [:environment, :load_config] do
db_namespace["drop"].invoke
db_namespace["setup"].invoke
end
# desc "Retrieves the charset for the current environment's database"
- task :charset => :environment do
+ task :charset => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when /mysql/
@@ -271,7 +160,7 @@ db_namespace = namespace :db do
end
# desc "Retrieves the collation for the current environment's database"
- task :collation => :environment do
+ task :collation => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when /mysql/
@@ -283,12 +172,12 @@ db_namespace = namespace :db do
end
desc 'Retrieves the current schema version number'
- task :version => :environment do
+ task :version => [:environment, :load_config] do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => :environment do
+ task :abort_if_pending_migrations => [:environment, :load_config] do
pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -311,20 +200,20 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :load => :environment do
+ task :load => [:environment, :load_config] do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :identify => :environment do
+ task :identify => [:environment, :load_config] do
require 'active_record/fixtures'
label, id = ENV['LABEL'], ENV['ID']
@@ -360,7 +249,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => :environment do
+ task :load => [:environment, :load_config] do
file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
load(file)
@@ -375,7 +264,7 @@ db_namespace = namespace :db do
namespace :cache do
desc 'Create a db/schema_cache.dump file.'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
@@ -385,7 +274,7 @@ db_namespace = namespace :db do
end
desc 'Clear a db/schema_cache.dump file.'
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
@@ -395,7 +284,7 @@ db_namespace = namespace :db do
namespace :structure do
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[Rails.env]['adapter']
@@ -506,19 +395,11 @@ db_namespace = namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
# desc "Empty the test database"
- task :purge => :environment do
+ task :purge => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
case abcs['test']['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test']))
- when /postgresql/
- ActiveRecord::Base.clear_active_connections!
- drop_database(abcs['test'])
- create_database(abcs['test'])
- when /sqlite/
- dbfile = abcs['test']['database']
- File.delete(dbfile) if File.exist?(dbfile)
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
when 'sqlserver'
test = abcs.deep_dup['test']
test_database = test['database']
@@ -548,7 +429,7 @@ db_namespace = namespace :db do
namespace :sessions do
# desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
- task :create => :environment do
+ task :create => [:environment, :load_config] do
raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
Rails.application.load_generators
require 'rails/generators/rails/session_migration/session_migration_generator'
@@ -556,7 +437,7 @@ db_namespace = namespace :db do
end
# desc "Clear the sessions table"
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}"
end
end
@@ -592,37 +473,6 @@ end
task 'test:prepare' => 'db:test:prepare'
-def drop_database(config)
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection.drop_database config['database']
- when /sqlite/
- require 'pathname'
- path = Pathname.new(config['database'])
- file = path.absolute? ? path.to_s : File.join(Rails.root, path)
-
- FileUtils.rm(file)
- when /postgresql/
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.drop_database config['database']
- end
-end
-
-def drop_database_and_rescue(config)
- begin
- drop_database(config)
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
-end
-
-def configs_for_environment
- environments = [Rails.env]
- environments << 'test' if Rails.env.development?
- ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? }
-end
-
def session_table_name
ActiveRecord::SessionStore::Session.table_name
end
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 836b15e2ce..960b78dc38 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -6,7 +6,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :_attr_readonly
+ class_attribute :_attr_readonly, instance_writer: false
self._attr_readonly = []
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index c380b5c029..ec13d27323 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -7,8 +7,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
- config_attribute :reflections
+ class_attribute :reflections
self.reflections = {}
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index db833fc7f1..af51c803a7 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -8,7 +8,7 @@ module ActiveRecord
included do
# Stores the default scope for the class
- config_attribute :default_scopes
+ class_attribute :default_scopes, instance_writer: false
self.default_scopes = []
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 41e3b92499..e8dd312a47 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,9 +1,21 @@
module ActiveRecord #:nodoc:
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :include_root_in_json, instance_accessor: false
+ self.include_root_in_json = true
+ end
+
# = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
+ included do
+ singleton_class.class_eval do
+ remove_method :include_root_in_json
+ delegate :include_root_in_json, to: 'ActiveRecord::Model'
+ end
+ end
+
def serializable_hash(options = nil)
options = options.try(:clone) || {}
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
new file mode 100644
index 0000000000..8e05c1a439
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -0,0 +1,87 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class DatabaseTasks # :nodoc:
+
+ TASKS_PATTERNS = {
+ /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks,
+ /postgresql/ => ActiveRecord::Tasks::PostgreSQLDatabaseTasks,
+ /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks
+ }
+ LOCAL_HOSTS = ['127.0.0.1', 'localhost']
+
+ def self.create(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).create
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't create database for #{configuration.inspect}"
+ end
+
+ def self.create_all
+ each_local_configuration { |configuration| create configuration }
+ end
+
+ def self.create_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ create configuration
+ }
+ ActiveRecord::Base.establish_connection environment
+ end
+
+ def self.drop(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).drop
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't drop #{configuration['database']}"
+ end
+
+ def self.drop_all
+ each_local_configuration { |configuration| drop configuration }
+ end
+
+ def self.drop_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ drop configuration
+ }
+ end
+
+ def self.purge(configuration)
+ class_for_adapter(configuration['adapter']).new(configuration).purge
+ end
+
+ private
+
+ def self.class_for_adapter(adapter)
+ key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] }
+ TASKS_PATTERNS[key]
+ end
+
+ def self.each_current_configuration(environment)
+ environments = [environment]
+ environments << 'test' if environment.development?
+
+ configurations = ActiveRecord::Base.configurations.values_at(*environments)
+ configurations.compact.each do |configuration|
+ yield configuration unless configuration['database'].blank?
+ end
+ end
+
+ def self.each_local_configuration
+ ActiveRecord::Base.configurations.each_value do |configuration|
+ next unless configuration['database']
+
+ if local_database?(configuration)
+ yield configuration
+ else
+ $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host."
+ end
+ end
+ end
+
+ def self.local_database?(configuration)
+ configuration['host'].in?(LOCAL_HOSTS) || configuration['host'].blank?
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
new file mode 100644
index 0000000000..646034f313
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -0,0 +1,93 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class MySQLDatabaseTasks # :nodoc:
+
+ DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
+ DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
+ ACCESS_DENIED_ERROR = 1045
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create
+ establish_connection configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ establish_connection configuration
+ rescue error_class => error
+ raise error unless error.errno == ACCESS_DENIED_ERROR
+
+ $stdout.print error.error
+ establish_connection root_configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ establish_connection configuration
+ rescue error_class => error
+ $stderr.puts error.error
+ $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
+ $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset']
+ end
+
+ def drop
+ establish_connection configuration
+ connection.drop_database configuration['database']
+ end
+
+ def purge
+ establish_connection :test
+ connection.recreate_database configuration['database'], creation_options
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def configuration_without_database
+ configuration.merge('database' => nil)
+ end
+
+ def creation_options
+ {
+ charset: (configuration['charset'] || DEFAULT_CHARSET),
+ collation: (configuration['collation'] || DEFAULT_COLLATION)
+ }
+ end
+
+ def error_class
+ case configuration['adapter']
+ when /jdbc/
+ require 'active_record/railties/jdbcmysql_error'
+ ArJdbcMySQL::Error
+ when /mysql2/
+ Mysql2::Error
+ else
+ Mysql::Error
+ end
+ end
+
+ def grant_statement
+ <<-SQL
+GRANT ALL PRIVILEGES ON #{configuration['database']}.*
+ TO '#{configuration['username']}'@'localhost'
+IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
+ SQL
+ end
+
+ def root_configuration_without_database
+ configuration_without_database.merge(
+ 'username' => 'root',
+ 'password' => root_password
+ )
+ end
+
+ def root_password
+ $stdout.print "Please provide the root password for your mysql installation\n>"
+ $stdin.gets.strip
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
new file mode 100644
index 0000000000..ddc5b9fc5c
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class PostgreSQLDatabaseTasks # :nodoc:
+
+ DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+
+ delegate :connection, :establish_connection, :clear_active_connections!,
+ to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create(master_established = false)
+ establish_master_connection unless master_established
+ connection.create_database configuration['database'],
+ configuration.merge('encoding' => encoding)
+ establish_connection configuration
+ end
+
+ def drop
+ establish_master_connection
+ connection.drop_database configuration['database']
+ end
+
+ def purge
+ clear_active_connections!
+ drop
+ create true
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def encoding
+ configuration['encoding'] || DEFAULT_ENCODING
+ end
+
+ def establish_master_connection
+ establish_connection configuration.merge(
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
new file mode 100644
index 0000000000..85f4c12829
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -0,0 +1,41 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SQLiteDatabaseTasks # :nodoc:
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration, root = Rails.root)
+ @configuration, @root = configuration, root
+ end
+
+ def create
+ if File.exist?(configuration['database'])
+ $stderr.puts "#{configuration['database']} already exists"
+ return
+ end
+
+ establish_connection configuration
+ connection
+ end
+
+ def drop
+ require 'pathname'
+ path = Pathname.new configuration['database']
+ file = path.absolute? ? path.to_s : File.join(root, path)
+
+ FileUtils.rm(file) if File.exist?(file)
+ end
+ alias :purge :drop
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def root
+ @root
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index c717fdea47..e5b7a6bfba 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,6 +1,11 @@
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :record_timestamps, instance_accessor: false
+ self.record_timestamps = true
+ end
+
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
@@ -33,8 +38,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :record_timestamps, :instance_writer => true
- self.record_timestamps = true
+ config_attribute :record_timestamps, instance_writer: true
end
def initialize_dup(other)
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 69dfd2503e..1199be68eb 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -1,6 +1,6 @@
module ActiveRecord
- class Base
- def self.fake_connection(config)
+ module ConnectionHandling
+ def fake_connection(config)
ConnectionAdapters::FakeAdapter.new nil, logger
end
end
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 98c38535a6..f4c40b8b97 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -15,6 +15,7 @@ module ActiveRecord
def self.active_record_super; Base; end
def self.base_class; self; end
+ extend ActiveRecord::Configuration
include ActiveRecord::AttributeMethods
def self.define_attribute_methods
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index dc99ac665c..17cb447105 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -7,12 +7,11 @@ module ActiveRecord
@handler = ConnectionHandler.new
@handler.establish_connection 'america', Base.connection_pool.spec
@klass = Class.new do
+ include Model::Tag
def self.name; 'america'; end
- class << self
- alias active_record_super superclass
- end
end
@subklass = Class.new(@klass) do
+ include Model::Tag
def self.name; 'north america'; end
end
end
diff --git a/activerecord/test/cases/database_tasks_test.rb b/activerecord/test/cases/database_tasks_test.rb
new file mode 100644
index 0000000000..503e26f549
--- /dev/null
+++ b/activerecord/test/cases/database_tasks_test.rb
@@ -0,0 +1,297 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class DatabaseTasksCreateTest < ActiveRecord::TestCase
+ def setup
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
+
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).
+ returns @postgresql_tasks
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ end
+
+ def test_mysql_create
+ @mysql_tasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'mysql'
+ end
+
+ def test_mysql2_create
+ @mysql_tasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'mysql2'
+ end
+
+ def test_postgresql_create
+ @postgresql_tasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'postgresql'
+ end
+
+ def test_sqlite_create
+ @sqlite_tasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'sqlite3'
+ end
+ end
+
+ class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {'development' => {'database' => 'my-db'}}
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations['development'].merge!('database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_ignores_remote_databases
+ @configurations['development'].merge!('host' => 'my.server.tld')
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_warning_for_remote_databases
+ @configurations['development'].merge!('host' => 'my.server.tld')
+
+ $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations['development'].merge!('host' => '127.0.0.1')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations['development'].merge!('host' => 'localhost')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations['development'].merge!('host' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+
+ class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ 'development' => {'database' => 'dev-db'},
+ 'test' => {'database' => 'test-db'},
+ 'production' => {'database' => 'prod-db'}
+ }
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'prod-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('production')
+ )
+ end
+
+ def test_creates_test_database_when_environment_is_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'dev-db')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'test-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+
+ def test_establishes_connection_for_the_given_environment
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
+
+ ActiveRecord::Base.expects(:establish_connection).with('development')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+ end
+
+ class DatabaseTasksDropTest < ActiveRecord::TestCase
+ def setup
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
+
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).
+ returns @postgresql_tasks
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ end
+
+ def test_mysql_create
+ @mysql_tasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'mysql'
+ end
+
+ def test_mysql2_create
+ @mysql_tasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'mysql2'
+ end
+
+ def test_postgresql_create
+ @postgresql_tasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'postgresql'
+ end
+
+ def test_sqlite_create
+ @sqlite_tasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'sqlite3'
+ end
+ end
+
+ class DatabaseTasksDropAllTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {:development => {'database' => 'my-db'}}
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations[:development].merge!('database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_ignores_remote_databases
+ @configurations[:development].merge!('host' => 'my.server.tld')
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_warning_for_remote_databases
+ @configurations[:development].merge!('host' => 'my.server.tld')
+
+ $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations[:development].merge!('host' => '127.0.0.1')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations[:development].merge!('host' => 'localhost')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations[:development].merge!('host' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+
+ class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ 'development' => {'database' => 'dev-db'},
+ 'test' => {'database' => 'test-db'},
+ 'production' => {'database' => 'prod-db'}
+ }
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'prod-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('production')
+ )
+ end
+
+ def test_creates_test_database_when_environment_is_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'dev-db')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'test-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+ end
+
+ class DatabaseTasksPurgeTest < ActiveRecord::TestCase
+ def setup
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
+
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).
+ returns @postgresql_tasks
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ end
+
+ def test_mysql_create
+ @mysql_tasks.expects(:purge)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'mysql'
+ end
+
+ def test_mysql2_create
+ @mysql_tasks.expects(:purge)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'mysql2'
+ end
+
+ def test_postgresql_create
+ @postgresql_tasks.expects(:purge)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'postgresql'
+ end
+
+ def test_sqlite_create
+ @sqlite_tasks.expects(:purge)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'sqlite3'
+ end
+ end
+end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 214546802a..73a01906b9 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -251,6 +251,65 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert !Task.new.respond_to?("#{method}=")
end
end
+
+ test "ActiveRecord::Model.whitelist_attributes works for models which include Model" do
+ begin
+ prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true
+
+ klass = Class.new { include ActiveRecord::Model }
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
+ ensure
+ ActiveRecord::Model.whitelist_attributes = prev
+ end
+ end
+
+ test "ActiveRecord::Model.whitelist_attributes works for models which inherit Base" do
+ begin
+ prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true
+
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
+
+ klass.attr_accessible 'foo'
+ assert_equal ['foo'], Class.new(klass).active_authorizers[:default].to_a
+ ensure
+ ActiveRecord::Model.whitelist_attributes = prev
+ end
+ end
+
+ test "ActiveRecord::Model.mass_assignment_sanitizer works for models which include Model" do
+ begin
+ sanitizer = Object.new
+ prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer
+
+ klass = Class.new { include ActiveRecord::Model }
+ assert_equal sanitizer, klass._mass_assignment_sanitizer
+
+ ActiveRecord::Model.mass_assignment_sanitizer = nil
+ klass = Class.new { include ActiveRecord::Model }
+ assert_not_nil klass._mass_assignment_sanitizer
+ ensure
+ ActiveRecord::Model.mass_assignment_sanitizer = prev
+ end
+ end
+
+ test "ActiveRecord::Model.mass_assignment_sanitizer works for models which inherit Base" do
+ begin
+ sanitizer = Object.new
+ prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer
+
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal sanitizer, klass._mass_assignment_sanitizer
+
+ sanitizer2 = Object.new
+ klass.mass_assignment_sanitizer = sanitizer2
+ assert_equal sanitizer2, Class.new(klass)._mass_assignment_sanitizer
+ ensure
+ ActiveRecord::Model.mass_assignment_sanitizer = prev
+ end
+ end
end
diff --git a/activerecord/test/cases/mysql_rake_test.rb b/activerecord/test/cases/mysql_rake_test.rb
new file mode 100644
index 0000000000..1dfd28df54
--- /dev/null
+++ b/activerecord/test/cases/mysql_rake_test.rb
@@ -0,0 +1,179 @@
+require 'cases/helper'
+require 'mysql'
+
+module ActiveRecord
+ class MysqlDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_without_database
+ ActiveRecord::Base.expects(:establish_connection).
+ with('adapter' => 'mysql', 'database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_default_options
+ @connection.expects(:create_database).
+ with('my-app-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_given_options
+ @connection.expects(:create_database).
+ with('my-app-db', {:charset => 'latin', :collation => 'latin_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge(
+ 'charset' => 'latin', 'collation' => 'latin_ci'
+ )
+ end
+
+ def test_establishes_connection_to_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true, :execute => true)
+ @error = Mysql::Error.new "Invalid permissions"
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'wossname'
+ }
+
+ $stdin.stubs(:gets).returns("secret\n")
+ $stdout.stubs(:print).returns(nil)
+ @error.stubs(:errno).returns(1045)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).raises(@error).then.
+ returns(true)
+ end
+
+ def test_root_password_is_requested
+ $stdin.expects(:gets).returns("secret\n")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_connection_established_as_root
+ ActiveRecord::Base.expects(:establish_connection).with({
+ 'adapter' => 'mysql',
+ 'database' => nil,
+ 'username' => 'root',
+ 'password' => 'secret'
+ })
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_database_created_by_root
+ @connection.expects(:create_database).
+ with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_grant_privileges_for_normal_user
+ @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_connection_established_as_normal_user
+ ActiveRecord::Base.expects(:establish_connection).returns do
+ ActiveRecord::Base.expects(:establish_connection).with({
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'secret'
+ })
+
+ raise @error
+ end
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_sends_output_to_stderr_when_other_errors
+ @error.stubs(:errno).returns(42)
+
+ $stderr.expects(:puts).at_least_once.returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class MySQLDBDropTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:drop_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with @configuration
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
+
+ class MySQLTestPurge < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:recreate_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_test_database
+ ActiveRecord::Base.expects(:establish_connection).with(:test)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_recreates_database_with_the_default_options
+ @connection.expects(:recreate_database).
+ with('test-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_recreates_database_with_the_given_options
+ @connection.expects(:recreate_database).
+ with('test-db', {:charset => 'latin', :collation => 'latin_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
+ 'charset' => 'latin', 'collation' => 'latin_ci'
+ )
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/postgresql_rake_test.rb b/activerecord/test/cases/postgresql_rake_test.rb
new file mode 100644
index 0000000000..58168e4948
--- /dev/null
+++ b/activerecord/test/cases/postgresql_rake_test.rb
@@ -0,0 +1,135 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class PostgreSQLDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_default_encoding
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_given_encoding
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'latin'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge('encoding' => 'latin')
+ end
+
+ def test_establishes_connection_to_new_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_db_create_with_error_prints_message
+ ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
+
+ $stderr.stubs(:puts).returns(true)
+ $stderr.expects(:puts).
+ with("Couldn't create database for #{@configuration.inspect}")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class PostgreSQLDBDropTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:drop_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
+
+ class PostgreSQLPurgeTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true, :drop_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:clear_active_connections!).returns(true)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_clears_active_connections
+ ActiveRecord::Base.expects(:clear_active_connections!)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_creates_database
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_establishes_connection
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/sqlite_rake_test.rb b/activerecord/test/cases/sqlite_rake_test.rb
new file mode 100644
index 0000000000..f68809bfda
--- /dev/null
+++ b/activerecord/test/cases/sqlite_rake_test.rb
@@ -0,0 +1,106 @@
+require 'cases/helper'
+require 'pathname'
+
+module ActiveRecord
+ class SqliteDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @database = 'db_create.sqlite3'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ File.stubs(:exist?).returns(false)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_checks_database_exists
+ File.expects(:exist?).with(@database).returns(false)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_when_file_exists
+ File.stubs(:exist?).returns(true)
+
+ $stderr.expects(:puts).with("#{@database} already exists")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_with_file_does_nothing
+ File.stubs(:exist?).returns(true)
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Base.expects(:establish_connection).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_establishes_a_connection
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_with_error_prints_message
+ ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
+
+ $stderr.stubs(:puts).returns(true)
+ $stderr.expects(:puts).
+ with("Couldn't create database for #{@configuration.inspect}")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteDBDropTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @path = stub(:to_s => '/absolute/path', :absolute? => true)
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ Pathname.stubs(:new).returns(@path)
+ File.stubs(:join).returns('/former/relative/path')
+ FileUtils.stubs(:rm).returns(true)
+ end
+
+ def test_creates_path_from_database
+ Pathname.expects(:new).with(@database).returns(@path)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_removes_file_with_absolute_path
+ File.stubs(:exist?).returns(true)
+ @path.stubs(:absolute?).returns(true)
+
+ FileUtils.expects(:rm).with('/absolute/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_generates_absolute_path_with_given_root
+ @path.stubs(:absolute?).returns(false)
+
+ File.expects(:join).with('/rails/root', @path).
+ returns('/former/relative/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_removes_file_with_relative_path
+ File.stubs(:exist?).returns(true)
+ @path.stubs(:absolute?).returns(false)
+
+ FileUtils.expects(:rm).with('/former/relative/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 30221f2401..fa38d5c1e3 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -21,5 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('multi_json', '~> 1.3')
- s.add_dependency('tzinfo', '~> 0.3.31')
+ s.add_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 070bfd7af6..efa2d43f20 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -107,7 +107,7 @@ class String
# Replaces underscores with dashes in the string.
#
- # 'puni_puni' # => "puni-puni"
+ # 'puni_puni'.dasherize # => "puni-puni"
def dasherize
ActiveSupport::Inflector.dasherize(self)
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 176edefa42..e3b4a7240e 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -10,10 +10,10 @@ module ActiveSupport
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
end
- self.deprecation_horizon = '3.2'
+ self.deprecation_horizon = '4.1'
# By default, warnings are not silenced and debugging is off.
self.silenced = false
self.debug = false
end
-end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index bea2ca17f1..d5f0e3fa6c 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -92,7 +92,7 @@ module ActiveSupport
method = message.split('.').first
begin
send(method, ActiveSupport::Notifications::Event.new(message, *args))
- rescue Exception => e
+ rescue => e
logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 678f551193..ef1711c60a 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -331,7 +331,7 @@ module ActiveSupport
def load
begin
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
- rescue Exception => e
+ rescue => e
raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 6735c561d3..e3d8cf48ce 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -33,7 +33,7 @@ module ActiveSupport
# end
#
# That code returns right away, you are just subscribing to "render" events.
- # The block will be called asynchronously whenever someone instruments "render":
+ # The block is saved and will be called whenever someone instruments "render":
#
# ActiveSupport::Notifications.instrument("render", :extra => :information) do
# render :text => "Foo"
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index 7941e655bb..dae25853cd 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -644,6 +644,14 @@ You should put the +root+ route at the top of the file, because it is the most p
NOTE: The +root+ route only routes +GET+ requests to the action.
+h4. Unicode character routes
+
+You can specify unicode character routes directly. For example
+
+<ruby>
+match 'こんにちは' => 'welcome#index'
+</ruby>
+
h3. Customizing Resourceful Routes
While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index 6cdc6ab289..4bf4751127 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -50,6 +50,8 @@ h4(#action_pack4_0). Action Pack
Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>ActionController::RoutingError</tt>.
+Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, e.g. <tt>get Rack::Utils.escape('こんにちは'), :controller => 'welcome', :action => 'index'</tt> to <tt>get 'こんにちは', :controller => 'welcome', :action => 'index'</tt>.
+
h4(#helpers_order). Helpers Loading Order
The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method.
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 7193dfdef5..c1aa98e481 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -148,6 +148,15 @@ module ApplicationTests
assert AppTemplate::Application.config.allow_concurrency
end
+ test "initialize a threadsafe app" do
+ add_to_config <<-RUBY
+ config.threadsafe!
+ RUBY
+
+ require "#{app_path}/config/application"
+ assert AppTemplate::Application.initialize!
+ end
+
test "asset_path defaults to nil for application" do
require "#{app_path}/config/environment"
assert_equal nil, AppTemplate::Application.config.asset_path
@@ -179,7 +188,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert !ActionController.autoload?(:Caching)
+ assert !ActionView.autoload?(:AssetPaths)
end
test "filter_parameters should be able to set via config.filter_parameters" do
@@ -374,9 +383,10 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert_equal ActiveModel::MassAssignmentSecurity::WhiteList,
- ActiveRecord::Base.active_authorizers[:default].class
- assert_equal [], ActiveRecord::Base.active_authorizers[:default].to_a
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
end
test "registers interceptors with ActionMailer" do
@@ -617,5 +627,26 @@ module ApplicationTests
make_basic_app
assert app.config.colorize_logging
end
+
+ test "config.active_record.observers" do
+ add_to_config <<-RUBY
+ config.active_record.observers = :foo_observer
+ RUBY
+
+ app_file 'app/models/foo.rb', <<-RUBY
+ class Foo < ActiveRecord::Base
+ end
+ RUBY
+
+ app_file 'app/models/foo_observer.rb', <<-RUBY
+ class FooObserver < ActiveRecord::Observer
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ ActiveRecord::Base
+ assert defined?(FooObserver)
+ end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index eac471a07e..e0ee349550 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -122,6 +122,18 @@ module ApplicationTests
assert_equal 0, ::AppTemplate::Application::User.count
end
+ def test_loading_only_yml_fixtures
+ Dir.chdir(app_path) do
+ `rake db:migrate`
+ end
+
+ app_file "test/fixtures/products.csv", ""
+
+ require "#{rails_root}/config/environment"
+ errormsg = Dir.chdir(app_path) { `rake db:fixtures:load` }
+ assert $?.success?, errormsg
+ end
+
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
`rails generate scaffold user username:string password:string;