aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb120
-rw-r--r--activerecord/lib/active_record/store.rb37
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb115
-rw-r--r--activerecord/test/cases/store_test.rb34
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb1
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb7
-rw-r--r--railties/lib/rails/generators/rails/controller/templates/controller.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb2
-rw-r--r--railties/test/generators/actions_test.rb8
-rw-r--r--railties/test/generators/namespaced_generators_test.rb4
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb4
15 files changed, 117 insertions, 224 deletions
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 450ef69744..c6699737b4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -2,6 +2,7 @@ require 'thread'
require 'monitor'
require 'set'
require 'active_support/core_ext/module/deprecation'
+require 'timeout'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -91,6 +92,21 @@ module ActiveRecord
attr_accessor :automatic_reconnect, :timeout
attr_reader :spec, :connections, :size, :reaper
+ class Latch # :nodoc:
+ def initialize
+ @mutex = Mutex.new
+ @cond = ConditionVariable.new
+ end
+
+ def release
+ @mutex.synchronize { @cond.broadcast }
+ end
+
+ def await
+ @mutex.synchronize { @cond.wait @mutex }
+ end
+ end
+
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
# host name, username, password, etc), as well as the maximum size for
@@ -112,25 +128,9 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ @latch = Latch.new
@connections = []
@automatic_reconnect = true
-
- # connections available to be checked out
- @available = []
-
- # number of threads waiting to check out a connection
- @num_waiting = 0
-
- # signal threads waiting
- @cond = new_cond
- end
-
- # Hack for tests to be able to add connections. Do not call outside of tests
- def insert_connection_for_test!(c)
- synchronize do
- @connections << c
- @available << c
- end
end
# Retrieve the connection associated with the current thread, or call
@@ -188,7 +188,6 @@ module ActiveRecord
conn.disconnect!
end
@connections = []
- @available = []
end
end
@@ -203,9 +202,6 @@ module ActiveRecord
@connections.delete_if do |conn|
conn.requires_reloading?
end
- @available.delete_if do |conn|
- conn.requires_reloading?
- end
end
end
@@ -229,19 +225,23 @@ module ActiveRecord
# Raises:
# - PoolFullError: no connection can be obtained from the pool.
def checkout
- synchronize do
- conn = nil
-
- if @num_waiting == 0
- conn = acquire_connection
- end
+ loop do
+ # Checkout an available connection
+ synchronize do
+ # Try to find a connection that hasn't been leased, and lease it
+ conn = connections.find { |c| c.lease }
+
+ # If all connections were leased, and we have room to expand,
+ # create a new connection and lease it.
+ if !conn && connections.size < size
+ conn = checkout_new_connection
+ conn.lease
+ end
- unless conn
- conn = wait_until(@timeout) { acquire_connection }
+ return checkout_and_verify(conn) if conn
end
- conn.lease
- checkout_and_verify(conn)
+ Timeout.timeout(@timeout, PoolFullError) { @latch.await }
end
end
@@ -257,10 +257,8 @@ module ActiveRecord
end
release conn
-
- @available.unshift conn
- @cond.signal
end
+ @latch.release
end
# Remove a connection from the connection pool. The connection will
@@ -268,14 +266,12 @@ module ActiveRecord
def remove(conn)
synchronize do
@connections.delete conn
- @available.delete conn
# FIXME: we might want to store the key on the connection so that removing
# from the reserved hash will be a little easier.
release conn
-
- @cond.signal # can make a new connection now
end
+ @latch.release
end
# Removes dead connections from the pool. A dead connection can occur
@@ -287,60 +283,12 @@ module ActiveRecord
connections.dup.each do |conn|
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
end
- @cond.broadcast # may violate fairness
end
+ @latch.release
end
private
- # Take an available connection or, if possible, create a new
- # one, or nil.
- #
- # Monitor must be held while calling this method.
- #
- # Returns: a newly acquired connection.
- def acquire_connection
- if @available.any?
- @available.pop
- elsif connections.size < size
- checkout_new_connection
- end
- end
-
- # Wait on +@cond+ until the block returns non-nil. Note that
- # unlike MonitorMixin::ConditionVariable#wait_until, this method
- # does not test the block before the first wait period.
- #
- # Monitor must be held when calling this method.
- #
- # +timeout+: Integer timeout in seconds
- #
- # Returns: the result of the block
- #
- # Raises:
- # - PoolFullError: timeout elapsed before +&block+ returned a connection
- def wait_until(timeout, &block)
- @num_waiting += 1
- begin
- t0 = Time.now
- loop do
- elapsed = Time.now - t0
- if elapsed >= timeout
- msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
- [timeout, elapsed]
- raise PoolFullError, msg
- end
-
- @cond.wait(timeout - elapsed)
-
- conn = yield
- return conn if conn
- end
- ensure
- @num_waiting -= 1
- end
- end
-
def release(conn)
thread_id = if @reserved_connections[current_connection_id] == conn
current_connection_id
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index ce2ea85ef9..fdd82b489a 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/indifferent_access'
+
module ActiveRecord
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
# It's like a simple key/value store backed into your record when you don't care about being able to
@@ -13,9 +15,6 @@ module ActiveRecord
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
#
- # String keys should be used for direct access to virtual attributes because of most of the coders do not
- # distinguish symbols and strings as keys.
- #
# Examples:
#
# class User < ActiveRecord::Base
@@ -23,8 +22,12 @@ module ActiveRecord
# end
#
# u = User.new(color: 'black', homepage: '37signals.com')
- # u.color # Accessor stored attribute
- # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor
+ # u.color # Accessor stored attribute
+ # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+ #
+ # # There is no difference between strings and symbols for accessing custom attributes
+ # u.settings[:country] # => 'Denmark'
+ # u.settings['country'] # => 'Denmark'
#
# # Add additional accessors to an existing store through store_accessor
# class SuperUser < User
@@ -35,24 +38,38 @@ module ActiveRecord
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, options.fetch(:coder, Hash)
+ serialize store_attribute, options.fetch(:coder, ActiveSupport::HashWithIndifferentAccess)
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
def store_accessor(store_attribute, *keys)
keys.flatten.each do |key|
define_method("#{key}=") do |value|
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key.to_s] = value
+ initialize_store_attribute(store_attribute)
+ send(store_attribute)[key] = value
send("#{store_attribute}_will_change!")
end
define_method(key) do
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key.to_s]
+ initialize_store_attribute(store_attribute)
+ send(store_attribute)[key]
end
end
end
end
+
+ private
+ def initialize_store_attribute(store_attribute)
+ case attribute = send(store_attribute)
+ when ActiveSupport::HashWithIndifferentAccess
+ # Already initialized. Do nothing.
+ when Hash
+ # Initialized as a Hash. Convert to indifferent access.
+ send :"#{store_attribute}=", attribute.with_indifferent_access
+ else
+ # Uninitialized. Set to an indifferent hash.
+ send :"#{store_attribute}=", ActiveSupport::HashWithIndifferentAccess.new
+ end
+ end
end
end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
index 3e3d6e2769..7dc6e8afcb 100644
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
@@ -36,7 +36,7 @@ module ActiveRecord
def test_close
pool = ConnectionPool.new(ConnectionSpecification.new({}, nil))
- pool.insert_connection_for_test! adapter
+ pool.connections << adapter
adapter.pool = pool
# Make sure the pool marks the connection in use
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index b1905515d3..8dc9f761c2 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -200,121 +200,6 @@ module ActiveRecord
end.join
end
- # The connection pool is "fair" if threads waiting for
- # connections receive them the order in which they began
- # waiting. This ensures that we don't timeout one HTTP request
- # even while well under capacity in a multi-threaded environment
- # such as a Java servlet container.
- #
- # We don't need strict fairness: if two connections become
- # available at the same time, it's fine of two threads that were
- # waiting acquire the connections out of order.
- #
- # Thus this test prepares waiting threads and then trickles in
- # available connections slowly, ensuring the wakeup order is
- # correct in this case.
- #
- # Try a few times since it might work out just by chance.
- def test_checkout_fairness
- 4.times { setup; do_checkout_fairness }
- end
-
- def do_checkout_fairness
- expected = (1..@pool.size).to_a.freeze
- # check out all connections so our threads start out waiting
- conns = expected.map { @pool.checkout }
- mutex = Mutex.new
- order = []
- errors = []
-
- threads = expected.map do |i|
- t = Thread.new {
- begin
- @pool.checkout # never checked back in
- mutex.synchronize { order << i }
- rescue => e
- mutex.synchronize { errors << e }
- end
- }
- Thread.pass until t.status == "sleep"
- t
- end
-
- # this should wake up the waiting threads one by one in order
- conns.each { |conn| @pool.checkin(conn); sleep 0.1 }
-
- threads.each(&:join)
-
- raise errors.first if errors.any?
-
- assert_equal(expected, order)
- end
-
- # As mentioned in #test_checkout_fairness, we don't care about
- # strict fairness. This test creates two groups of threads:
- # group1 whose members all start waiting before any thread in
- # group2. Enough connections are checked in to wakeup all
- # group1 threads, and the fact that only group1 and no group2
- # threads acquired a connection is enforced.
- #
- # Try a few times since it might work out just by chance.
- def test_checkout_fairness_by_group
- 4.times { setup; do_checkout_fairness_by_group }
- end
-
- def do_checkout_fairness_by_group
- @pool.instance_variable_set(:@size, 10)
- # take all the connections
- conns = (1..10).map { @pool.checkout }
- mutex = Mutex.new
- successes = [] # threads that successfully got a connection
- errors = []
-
- make_thread = proc do |i|
- t = Thread.new {
- begin
- @pool.checkout # never checked back in
- mutex.synchronize { successes << i }
- rescue => e
- mutex.synchronize { errors << e }
- end
- }
- Thread.pass until t.status == "sleep"
- t
- end
-
- # all group1 threads start waiting before any in group2
- group1 = (1..5).map(&make_thread)
- group2 = (6..10).map(&make_thread)
-
- # checkin n connections back to the pool
- checkin = proc do |n|
- n.times do
- c = conns.pop
- @pool.checkin(c)
- end
- end
-
- checkin.call(group1.size) # should wake up all group1
-
- loop do
- sleep 0.1
- break if mutex.synchronize { (successes.size + errors.size) == group1.size }
- end
-
- winners = mutex.synchronize { successes.dup }
- checkin.call(group2.size) # should wake up everyone remaining
-
- group1.each(&:join)
- group2.each(&:join)
-
- assert_equal((1..group1.size).to_a, winners.sort)
-
- if errors.any?
- raise errors.first
- end
- end
-
def test_automatic_reconnect=
pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
assert pool.automatic_reconnect
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index e1d0f1f799..3a5d84df9f 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -41,6 +41,40 @@ class StoreTest < ActiveRecord::TestCase
assert_equal false, @john.remember_login
end
+ test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do
+ @john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy')
+ @john.height = 'low'
+ assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
+ assert_equal 'low', @john.json_data[:height]
+ assert_equal 'low', @john.json_data['height']
+ assert_equal 'heavy', @john.json_data[:weight]
+ assert_equal 'heavy', @john.json_data['weight']
+ end
+
+ test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do
+ @john.json_data = { :height => 'tall', 'weight' => 'heavy' }
+ assert_equal true, @john.json_data.instance_of?(Hash)
+ assert_equal 'tall', @john.json_data[:height]
+ assert_equal nil, @john.json_data['height']
+ assert_equal nil, @john.json_data[:weight]
+ assert_equal 'heavy', @john.json_data['weight']
+ @john.height = 'low'
+ assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
+ assert_equal 'low', @john.json_data[:height]
+ assert_equal 'low', @john.json_data['height']
+ assert_equal 'heavy', @john.json_data[:weight]
+ assert_equal 'heavy', @john.json_data['weight']
+ end
+
+ test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do
+ @john.json_data = "somedata"
+ @john.height = 'low'
+ assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
+ assert_equal 'low', @john.json_data[:height]
+ assert_equal 'low', @john.json_data['height']
+ assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any?
+ end
+
test "reading store attributes through accessors encoded with JSON" do
assert_equal 'tall', @john.height
assert_nil @john.weight
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 8f018dcbc6..56d6676961 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -25,6 +25,7 @@ require 'securerandom'
require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
+require "active_support/lazy_load_hooks"
module ActiveSupport
extend ActiveSupport::Autoload
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index a1626ebeba..4045db3232 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -1,5 +1,4 @@
require "active_support/inflector/methods"
-require "active_support/lazy_load_hooks"
module ActiveSupport
module Autoload
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 6cd2ea2bbd..92f40b9e31 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -27,7 +27,7 @@ module Rails
log :gemfile, message
options.each do |option, value|
- parts << ":#{option} => #{value.inspect}"
+ parts << "#{option}: #{value.inspect}"
end
in_root do
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 286e93c3cf..303e47877f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,6 +1,10 @@
<%= app_const %>.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
+
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ # root :to => 'welcome#index'
# Sample of regular route:
# get 'products/:id' => 'catalog#view'
@@ -46,9 +50,6 @@
# resources :products
# end
- # You can have the root of your site routed with "root"
- # just remember to delete public/index.html.
- # root :to => 'welcome#index'
# See how all your routes lay out with "rake routes"
end \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb
index 9b04192126..ece6bbba3b 100644
--- a/railties/lib/rails/generators/rails/controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb
@@ -1,5 +1,5 @@
<% if namespaced? -%>
-require "<%= namespaced_file_path %>/application_controller"
+require_dependency "<%= namespaced_file_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 722e37e20b..7088367462 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -139,7 +139,7 @@ task :default => :test
gemfile_in_app_path = File.join(rails_app_path, "Gemfile")
if File.exist? gemfile_in_app_path
- entry = "gem '#{name}', :path => '#{relative_path}'"
+ entry = "gem '#{name}', path: '#{relative_path}'"
append_file gemfile_in_app_path, entry
end
end
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 5cd31bf2ee..0294bde582 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -1,5 +1,5 @@
<% if namespaced? -%>
-require "<%= namespaced_file_path %>/application_controller"
+require_dependency "<%= namespaced_file_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index a8c8fcd5b7..bc086c5986 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -67,6 +67,14 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'Gemfile', /^gem "rspec-rails"$/
end
+ def test_gem_should_include_options
+ run_generator
+
+ action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1'
+
+ assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/
+ end
+
def test_gem_group_should_wrap_gems_in_a_group
run_generator
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 903465392d..09169ef2d2 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -21,7 +21,7 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
def test_namespaced_controller_skeleton_is_created
run_generator
assert_file "app/controllers/test_app/account_controller.rb",
- /require "test_app\/application_controller"/,
+ /require_dependency "test_app\/application_controller"/,
/module TestApp/,
/ class AccountController < ApplicationController/
@@ -234,7 +234,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Controller
assert_file "app/controllers/test_app/product_lines_controller.rb",
- /require "test_app\/application_controller"/,
+ /require_dependency "test_app\/application_controller"/,
/module TestApp/,
/class ProductLinesController < ApplicationController/
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 51374e5f3f..58740978aa 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -279,7 +279,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root]
- assert_file gemfile_path, /gem 'bukkits', :path => 'tmp\/bukkits'/
+ assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/
ensure
Object.send(:remove_const, 'APP_PATH')
FileUtils.rm gemfile_path
@@ -294,7 +294,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-gemfile-entry"]
assert_file gemfile_path do |contents|
- assert_no_match(/gem 'bukkits', :path => 'tmp\/bukkits'/, contents)
+ assert_no_match(/gem 'bukkits', path: 'tmp\/bukkits'/, contents)
end
ensure
Object.send(:remove_const, 'APP_PATH')