aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb47
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb25
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb10
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb2
-rw-r--r--activemodel/test/cases/mass_assignment_security/permission_set_test.rb6
-rw-r--r--activerecord/CHANGELOG.md3
-rw-r--r--activerecord/Rakefile10
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb211
-rw-r--r--activerecord/lib/active_record/core.rb19
-rw-r--r--activerecord/lib/active_record/migration.rb32
-rw-r--r--activerecord/lib/active_record/railtie.rb7
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb10
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/test_case.rb50
-rw-r--r--activerecord/test/cases/associations/eager_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb104
-rw-r--r--activerecord/test/cases/finder_test.rb13
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb22
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb17
-rw-r--r--activerecord/test/cases/named_scope_test.rb10
-rw-r--r--activerecord/test/cases/query_cache_test.rb7
-rw-r--r--activerecord/test/models/person.rb21
-rw-r--r--activesupport/CHANGELOG.md2
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb12
-rw-r--r--activesupport/lib/active_support/dependencies.rb22
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb19
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb23
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb2
-rw-r--r--activesupport/test/dependencies_test.rb4
-rw-r--r--activesupport/test/test_test.rb82
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile2
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Rakefile2
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/test/application/configuration_test.rb17
-rw-r--r--railties/test/application/rake_test.rb11
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb18
51 files changed, 669 insertions, 232 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 815a46a3ca..8c2d887cb3 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Return proper format on exceptions. *Santiago Pastorino*
+
* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
* Include mounted_helpers (helpers for accessing mounted engines) in ActionDispatch::IntegrationTest by default. *Piotr Sarnacki*
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 0eaae80461..ee1913dbf9 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -99,9 +99,9 @@ module Mime
end
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms))
+ Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
- SET << Mime.const_get(symbol.to_s.upcase)
+ SET << Mime.const_get(symbol.upcase)
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
@@ -194,7 +194,7 @@ module Mime
#
# Mime::Type.unregister(:mobile)
def unregister(symbol)
- symbol = symbol.to_s.upcase
+ symbol = symbol.upcase
mime = Mime.const_get(symbol)
Mime.instance_eval { remove_const(symbol) }
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 85b8d178bf..204f3f7fb3 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -8,23 +8,44 @@ module ActionDispatch
end
def call(env)
- status = env["PATH_INFO"][1..-1]
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html"
-
- if locale_path && File.exist?(locale_path)
- render(status, File.read(locale_path))
- elsif File.exist?(path)
- render(status, File.read(path))
+ exception = env["action_dispatch.exception"]
+ 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)
+ end
+
+ private
+
+ def render(status, body, options)
+ format = options[:format]
+
+ if format && body.respond_to?(format)
+ render_format(status, body.public_send(format), options)
else
- [404, { "X-Cascade" => "pass" }, []]
+ render_html(status)
end
end
- private
+ def render_format(status, body, options)
+ [status, {'Content-Type' => "#{options[:content_type]}; charset=#{ActionDispatch::Response.default_charset}",
+ 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def render_html(status)
+ found = false
+ path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ 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]]
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 13ab19ed8f..351b9c4cfa 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -68,4 +68,29 @@ module ShowExceptions
assert_match(/boom/, body)
end
end
+
+ class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
+ def test_render_json_exception
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'application/json'
+ assert_response :internal_server_error
+ assert_equal 'application/json', response.content_type.to_s
+ assert_equal({ :status => '500', :error => 'boom!' }.to_json, response.body)
+ end
+
+ def test_render_xml_exception
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
+ assert_response :internal_server_error
+ assert_equal 'application/xml', response.content_type.to_s
+ assert_equal({ :status => '500', :error => 'boom!' }.to_xml, response.body)
+ end
+
+ def test_render_fallback_exception
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
+ assert_response :internal_server_error
+ assert_equal 'text/html', response.content_type.to_s
+ end
+ end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index e3f9faaa64..9d77c3acc5 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -148,11 +148,11 @@ class MimeTypeTest < ActiveSupport::TestCase
types = Mime::SET.symbols.uniq - [:all, :iphone]
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
+ types.delete_if { |type| !Mime.const_defined?(type.upcase) }
types.each do |type|
- mime = Mime.const_get(type.to_s.upcase)
+ mime = Mime.const_get(type.upcase)
assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
invalid_types = types - [type]
@@ -170,10 +170,10 @@ class MimeTypeTest < ActiveSupport::TestCase
all_types = Mime::SET.symbols
all_types.uniq!
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
+ all_types.delete_if { |type| !Mime.const_defined?(type.upcase) }
verified, unverified = all_types.partition { |type| Mime::Type.browser_generated_types.include? type }
- assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" }
- assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" }
+ assert verified.each { |type| assert Mime.const_get(type.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" }
+ assert unverified.each { |type| assert !Mime.const_get(type.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" }
end
test "references gives preference to symbols before strings" do
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 9661349503..415ab0ad17 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -5,7 +5,7 @@ module ActiveModel
class PermissionSet < Set
def +(values)
- super(values.map(&:to_s))
+ super(values.compact.map(&:to_s))
end
def include?(key)
diff --git a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
index d005b638e4..8082c49852 100644
--- a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
@@ -13,6 +13,12 @@ class PermissionSetTest < ActiveModel::TestCase
assert new_list.include?('admin'), "did not add collection to #{@permission_list.inspect}}"
end
+ test "+ compacts added collection values" do
+ added_collection = [ nil ]
+ new_list = @permission_list + added_collection
+ assert_equal new_list, @permission_list, "did not add collection to #{@permission_list.inspect}}"
+ end
+
test "include? normalizes multi-parameter keys" do
multi_param_key = 'admin(1)'
new_list = @permission_list += [ 'admin' ]
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 55cc85a63d..9daae27b36 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,8 @@
## Rails 4.0.0 (unreleased) ##
+* Added `ActiveRecord::Migration.check_pending!` that raises an error if
+ migrations are pending. *Richard Schneeman*
+
* Added `#destroy!` which acts like `#destroy` but will raise an
`ActiveRecord::RecordNotDestroyed` exception instead of returning `false`.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 7feb0b75a0..a29d7b0e99 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -114,6 +114,16 @@ namespace :postgresql do
config = ARTest.config['connections']['postgresql']
%x( createdb -E UTF8 #{config['arunit']['database']} )
%x( createdb -E UTF8 #{config['arunit2']['database']} )
+
+ # prepare hstore
+ version = %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2")
+ %w(arunit arunit2).each do |db|
+ if version < "9.1.0"
+ puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
+ else
+ %x( psql #{config[db]['database']} -c "CREATE EXTENSION hstore;" )
+ end
+ end
end
desc 'Drop the PostgreSQL test databases'
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index ca5f7b9c6c..abc2fa546a 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -69,6 +69,7 @@ module ActiveRecord
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
nested_parameter_attributes = []
+ previous_options = @mass_assignment_options
@mass_assignment_options = options
unless options[:without_protection]
@@ -94,8 +95,9 @@ module ActiveRecord
send("#{k}=", v)
end
- @mass_assignment_options = nil
assign_multiparameter_attributes(multi_parameter_attributes)
+ ensure
+ @mass_assignment_options = previous_options
end
protected
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 39ea885246..172026d150 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -149,7 +149,9 @@ module ActiveRecord
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
def attributes
- Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
+ attribute_names.each_with_object({}) { |name, attrs|
+ attrs[name] = read_attribute(name)
+ }
end
# Returns an <tt>#inspect</tt>-like string for the value of the
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 c259e46073..61ff603006 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -2,7 +2,6 @@ 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
@@ -70,6 +69,131 @@ module ActiveRecord
# after which the Reaper will consider a connection reapable. (default
# 5 seconds).
class ConnectionPool
+ # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
+ # with which it shares a Monitor. But could be a generic Queue.
+ #
+ # The Queue in stdlib's 'thread' could replace this class except
+ # stdlib's doesn't support waiting with a timeout.
+ class Queue
+ def initialize(lock = Monitor.new)
+ @lock = lock
+ @cond = @lock.new_cond
+ @num_waiting = 0
+ @queue = []
+ end
+
+ # Test if any threads are currently waiting on the queue.
+ def any_waiting?
+ synchronize do
+ @num_waiting > 0
+ end
+ end
+
+ # Return the number of threads currently waiting on this
+ # queue.
+ def num_waiting
+ synchronize do
+ @num_waiting
+ end
+ end
+
+ # Add +element+ to the queue. Never blocks.
+ def add(element)
+ synchronize do
+ @queue.push element
+ @cond.signal
+ end
+ end
+
+ # If +element+ is in the queue, remove and return it, or nil.
+ def delete(element)
+ synchronize do
+ @queue.delete(element)
+ end
+ end
+
+ # Remove all elements from the queue.
+ def clear
+ synchronize do
+ @queue.clear
+ end
+ end
+
+ # Remove the head of the queue.
+ #
+ # If +timeout+ is not given, remove and return the head the
+ # queue if the number of available elements is strictly
+ # greater than the number of threads currently waiting (that
+ # is, don't jump ahead in line). Otherwise, return nil.
+ #
+ # If +timeout+ is given, block if it there is no element
+ # available, waiting up to +timeout+ seconds for an element to
+ # become available.
+ #
+ # Raises:
+ # - ConnectionTimeoutError if +timeout+ is given and no element
+ # becomes available after +timeout+ seconds,
+ def poll(timeout = nil)
+ synchronize do
+ if timeout
+ no_wait_poll || wait_poll(timeout)
+ else
+ no_wait_poll
+ end
+ end
+ end
+
+ private
+
+ def synchronize(&block)
+ @lock.synchronize(&block)
+ end
+
+ # Test if the queue currently contains any elements.
+ def any?
+ !@queue.empty?
+ end
+
+ # A thread can remove an element from the queue without
+ # waiting if an only if the number of currently available
+ # connections is strictly greater than the number of waiting
+ # threads.
+ def can_remove_no_wait?
+ @queue.size > @num_waiting
+ end
+
+ # Removes and returns the head of the queue if possible, or nil.
+ def remove
+ @queue.shift
+ end
+
+ # Remove and return the head the queue if the number of
+ # available elements is strictly greater than the number of
+ # threads currently waiting. Otherwise, return nil.
+ def no_wait_poll
+ remove if can_remove_no_wait?
+ end
+
+ # Waits on the queue up to +timeout+ seconds, then removes and
+ # returns the head of the queue.
+ def wait_poll(timeout)
+ @num_waiting += 1
+
+ t0 = Time.now
+ elapsed = 0
+ loop do
+ @cond.wait(timeout - elapsed)
+
+ return remove if any?
+
+ elapsed = Time.now - t0
+ raise ConnectionTimeoutError if elapsed >= timeout
+ end
+ ensure
+ @num_waiting -= 1
+ end
+ end
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
# A reaper instantiated with a nil frequency will never reap the
# connection pool.
@@ -100,21 +224,6 @@ module ActiveRecord
attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_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
@@ -137,9 +246,18 @@ 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
+
+ @available = Queue.new self
+ end
+
+ # Hack for tests to be able to add connections. Do not call outside of tests
+ def insert_connection_for_test!(c) #:nodoc:
+ synchronize do
+ @connections << c
+ @available.add c
+ end
end
# Retrieve the connection associated with the current thread, or call
@@ -197,6 +315,7 @@ module ActiveRecord
conn.disconnect!
end
@connections = []
+ @available.clear
end
end
@@ -211,6 +330,10 @@ module ActiveRecord
@connections.delete_if do |conn|
conn.requires_reloading?
end
+ @available.clear
+ @connections.each do |conn|
+ @available.add conn
+ end
end
end
@@ -234,23 +357,10 @@ module ActiveRecord
# Raises:
# - PoolFullError: no connection can be obtained from the pool.
def checkout
- 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
-
- return checkout_and_verify(conn) if conn
- end
-
- Timeout.timeout(@checkout_timeout, PoolFullError) { @latch.await }
+ synchronize do
+ conn = acquire_connection
+ conn.lease
+ checkout_and_verify(conn)
end
end
@@ -266,8 +376,9 @@ module ActiveRecord
end
release conn
+
+ @available.add conn
end
- @latch.release
end
# Remove a connection from the connection pool. The connection will
@@ -275,12 +386,14 @@ 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
+
+ @available.add checkout_new_connection if @available.any_waiting?
end
- @latch.release
end
# Removes dead connections from the pool. A dead connection can occur
@@ -293,11 +406,35 @@ module ActiveRecord
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
end
end
- @latch.release
end
private
+ # Acquire a connection by one of 1) immediately removing one
+ # from the queue of available connections, 2) creating a new
+ # connection if the pool is not at capacity, 3) waiting on the
+ # queue for a connection to become available.
+ #
+ # Raises:
+ # - PoolFullError if a connection could not be acquired (FIXME:
+ # why not ConnectionTimeoutError?
+ def acquire_connection
+ if conn = @available.poll
+ conn
+ elsif @connections.size < @size
+ checkout_new_connection
+ else
+ t0 = Time.now
+ begin
+ @available.poll(@checkout_timeout)
+ rescue ConnectionTimeoutError
+ msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
+ [@checkout_timeout, Time.now - t0]
+ raise PoolFullError, msg
+ end
+ end
+ end
+
def release(conn)
thread_id = if @reserved_connections[current_connection_id] == conn
current_connection_id
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1fa6c701bb..dbad561ca2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -380,15 +380,16 @@ module ActiveRecord
@attributes[pk] = nil unless @attributes.key?(pk)
- @aggregation_cache = {}
- @association_cache = {}
- @attributes_cache = {}
- @previously_changed = {}
- @changed_attributes = {}
- @readonly = false
- @destroyed = false
- @marked_for_destruction = false
- @new_record = true
+ @aggregation_cache = {}
+ @association_cache = {}
+ @attributes_cache = {}
+ @previously_changed = {}
+ @changed_attributes = {}
+ @readonly = false
+ @destroyed = false
+ @marked_for_destruction = false
+ @new_record = true
+ @mass_assignment_options = nil
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ac4f53c774..acec65991d 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -32,6 +32,12 @@ module ActiveRecord
end
end
+ class PendingMigrationError < ActiveRecordError#:nodoc:
+ def initialize
+ super("Migrations are pending run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve the issue")
+ end
+ end
+
# = Active Record Migrations
#
# Migrations can manage the evolution of a schema used by several physical
@@ -326,10 +332,28 @@ module ActiveRecord
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
+
+ # This class is used to verify that all migrations have been run before
+ # loading a web page if config.active_record.migration_error is set to :page_load
+ class CheckPending
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ ActiveRecord::Migration.check_pending!
+ @app.call(env)
+ end
+ end
+
class << self
attr_accessor :delegate # :nodoc:
end
+ def self.check_pending!
+ raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
+ end
+
def self.method_missing(name, *args, &block) # :nodoc:
(delegate || superclass.delegate).send(name, *args, &block)
end
@@ -605,6 +629,14 @@ module ActiveRecord
end
end
+ def needs_migration?
+ current_version < last_version
+ end
+
+ def last_version
+ migrations(migrations_paths).last.try(:version)||0
+ end
+
def proper_table_name(name)
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 1e497b2a79..319516413b 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -59,6 +59,13 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
+ initializer "active_record.migration_error" do |app|
+ if config.active_record.delete(:migration_error) == :page_load
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks",
+ "ActiveRecord::Migration::CheckPending"
+ end
+ end
+
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
if app.config.active_record.delete(:whitelist_attributes)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index f5fdf437bf..64dda4f35a 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -32,12 +32,12 @@ module ActiveRecord
protected
def method_missing(method, *args, &block)
- if Array.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
- to_a.send(method, *args, &block)
- elsif @klass.respond_to?(method)
+ if @klass.respond_to?(method)
::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
scoping { @klass.send(method, *args, &block) }
+ elsif Array.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
::ActiveRecord::Delegation.delegate method, :to => :arel
arel.send(method, *args, &block)
@@ -46,4 +46,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 5f6898b45a..c91758265b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -106,7 +106,7 @@ module ActiveRecord
# Person.last # returns the last object fetched by SELECT * FROM people
# Person.where(["user_name = ?", user_name]).last
# Person.order("created_on DESC").offset(5).last
- # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
#
# Take note that in that last case, the results are sorted in ascending order:
#
@@ -176,7 +176,7 @@ module ActiveRecord
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- relation = relation.except(:select, :order).select("1").limit(1)
+ relation = relation.except(:select, :order).select("1 AS one").limit(1)
case id
when Array, Hash
@@ -186,6 +186,8 @@ module ActiveRecord
end
connection.select_value(relation, "#{name} Exists", relation.bind_values)
+ rescue ThrowResult
+ false
end
protected
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index fcaa4b74a6..c7a6c37d50 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -8,7 +8,7 @@ module ActiveRecord
# Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
def teardown
- SQLCounter.log.clear
+ SQLCounter.clear_log
end
def assert_date_from_db(expected, actual, message = nil)
@@ -22,47 +22,57 @@ module ActiveRecord
end
def assert_sql(*patterns_to_match)
- SQLCounter.log = []
+ SQLCounter.clear_log
yield
- SQLCounter.log
+ SQLCounter.log_all
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless SQLCounter.log.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
end
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
- def assert_queries(num = 1)
- SQLCounter.log = []
+ def assert_queries(num = 1, options = {})
+ ignore_none = options.fetch(:ignore_none) { num == :any }
+ SQLCounter.clear_log
yield
ensure
- assert_equal num, SQLCounter.log.size, "#{SQLCounter.log.size} instead of #{num} queries were executed.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
+ the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
+ if num == :any
+ assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
+ else
+ mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
+ assert_equal num, the_log.size, mesg
+ end
end
def assert_no_queries(&block)
- prev_ignored_sql = SQLCounter.ignored_sql
- SQLCounter.ignored_sql = []
- assert_queries(0, &block)
- ensure
- SQLCounter.ignored_sql = prev_ignored_sql
+ assert_queries(0, :ignore_none => true, &block)
end
end
class SQLCounter
class << self
- attr_accessor :ignored_sql, :log
+ attr_accessor :ignored_sql, :log, :log_all
+ def clear_log; self.log = []; self.log_all = []; end
end
- self.log = []
+ self.clear_log
self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
# FIXME: this needs to be refactored so specific database can add their own
- # ignored SQL. This ignored SQL is for Oracle.
- ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
-
+ # ignored SQL, or better yet, use a different notification for the queries
+ # instead examining the SQL content.
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
+ mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/]
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im]
+
+ [oracle_ignored, mysql_ignored, postgresql_ignored].each do |db_ignored_sql|
+ ignored_sql.concat db_ignored_sql
+ end
attr_reader :ignore
@@ -75,8 +85,10 @@ module ActiveRecord
# FIXME: this seems bad. we should probably have a better way to indicate
# the query was cached
- return if 'CACHE' == values[:name] || ignore =~ sql
- self.class.log << sql
+ return if 'CACHE' == values[:name]
+
+ self.class.log_all << sql
+ self.class.log << sql unless ignore =~ sql
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 08467900f9..31b11ee334 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -29,11 +29,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors
- def setup
- # preheat table existence caches
- Comment.find_by_id(1)
- end
-
def test_eager_with_has_one_through_join_model_with_conditions_on_the_through
member = Member.scoped(:includes => :favourite_club).find(members(:some_other_guy).id)
assert_nil member.favourite_club
@@ -983,7 +978,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.scoped(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').all
end
assert_equal posts(:welcome, :thinking), posts
-
end
def test_eager_loading_with_conditions_on_string_joined_table_preloads
@@ -998,7 +992,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
-
end
def test_eager_loading_with_select_on_joined_table_preloads
@@ -1010,8 +1003,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_loading_with_conditions_on_join_model_preloads
- Author.columns
-
authors = assert_queries(2) do
Author.scoped(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").all
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index ed1caa2ef5..9d693bae0c 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -817,11 +817,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
# clear cache possibly created by other tests
david.projects.reset_column_information
- assert_queries(1) { david.projects.columns; david.projects.columns }
+ assert_queries(:any) { david.projects.columns }
+ assert_no_queries { david.projects.columns }
## and again to verify that reset_column_information clears the cache correctly
david.projects.reset_column_information
- assert_queries(1) { david.projects.columns; david.projects.columns }
+
+ assert_queries(:any) { david.projects.columns }
+ assert_no_queries { david.projects.columns }
end
def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 0d8f311117..b8481175b2 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1358,7 +1358,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal author.essays, Essay.where(writer_id: "David")
-
end
def test_has_many_custom_primary_key
@@ -1438,7 +1437,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
:group => "#{Namespaced::Firm.table_name}.id"
).find firm.id
assert_equal 1, stats.num_clients.to_i
-
ensure
ActiveRecord::Base.store_full_sti_class = old
end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
index 7dc6e8afcb..3e3d6e2769 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.connections << adapter
+ pool.insert_connection_for_test! 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 bba7815d73..8287b35aaf 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -200,6 +200,110 @@ 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.
+ def test_checkout_fairness
+ @pool.instance_variable_set(:@size, 10)
+ 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.
+ def test_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/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 8c16972c12..aa44307bc2 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -45,6 +45,12 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(NoMethodError) { Topic.exists?([1,2]) }
end
+ def test_exists_does_not_select_columns_without_alias
+ assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
+ Topic.exists?
+ end
+ end
+
def test_exists_returns_true_with_one_record_and_no_args
assert Topic.exists?
end
@@ -63,7 +69,12 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.order(:id).uniq.exists?
end
- def test_does_not_exist_with_empty_table_and_no_args_given
+ def test_exists_with_includes_limit_and_empty_result
+ assert !Topic.includes(:replies).limit(0).exists?
+ assert !Topic.includes(:replies).limit(1).where('0 = 1').exists?
+ end
+
+ def test_exists_with_empty_table_and_no_args_given
Topic.delete_all
assert !Topic.exists?
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index fc89ced830..b35dc2d26d 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -882,4 +882,26 @@ class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase
assert_all_attributes(person.best_friends.first)
end
+ def test_mass_assignment_options_are_reset_after_exception
+ person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin)
+ person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin)
+
+ attributes = { :best_friend_attributes => { :comments => 'rides a sweet bike' } }
+ assert_raises(RuntimeError) { person.assign_attributes(attributes, :as => :admin) }
+ assert_equal 'm', person.best_friend.gender
+
+ person.best_friend_attributes = { :gender => 'f' }
+ assert_equal 'm', person.best_friend.gender
+ end
+
+ def test_mass_assignment_options_are_nested_correctly
+ person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin)
+ person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin)
+
+ attributes = { :best_friend_first_name => 'Josh', :best_friend_attributes => { :gender => 'f' } }
+ person.assign_attributes(attributes, :as => :admin)
+ assert_equal 'Josh', person.best_friend.first_name
+ assert_equal 'f', person.best_friend.gender
+ end
+
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 18f8d82bfe..3014bbe273 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -174,7 +174,7 @@ module ActiveRecord
assert_not_equal "Z", bob.moment_of_truth.zone
# US/Eastern is -5 hours from GMT
assert_equal Rational(-5, 24), bob.moment_of_truth.offset
- assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM
+ assert_match(/\A-05:00\Z/, bob.moment_of_truth.zone)
assert_equal DateTime::ITALY, bob.moment_of_truth.start
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 5d1bad0d54..3c0d2b18d9 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -56,6 +56,21 @@ class MigrationTest < ActiveRecord::TestCase
Person.reset_column_information
end
+ def test_migrator_versions
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal 3, ActiveRecord::Migrator.current_version
+ assert_equal 3, ActiveRecord::Migrator.last_version
+ assert_equal false, ActiveRecord::Migrator.needs_migration?
+
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
+ assert_equal 0, ActiveRecord::Migrator.current_version
+ assert_equal 3, ActiveRecord::Migrator.last_version
+ assert_equal true, ActiveRecord::Migrator.needs_migration?
+ end
+
def test_create_table_with_force_true_does_not_drop_nonexisting_table
if Person.connection.table_exists?(:testings2)
Person.connection.drop_table :testings2
@@ -523,7 +538,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
# One query for columns (delete_me table)
# One query for primary key (delete_me table)
# One query to do the bulk change
- assert_queries(3) do
+ assert_queries(3, :ignore_none => true) do
with_bulk_change_table do |t|
t.change :name, :string, :default => 'NONAME'
t.change :birthdate, :datetime
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index bf825c002a..c886160af3 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -19,7 +19,6 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_found_items_are_cached
- Topic.columns
all_posts = Topic.base
assert_queries(1) do
@@ -46,6 +45,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
end
+ def test_method_missing_priority_when_delegating
+ klazz = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ scope :since, Proc.new { where('written_on >= ?', Time.now - 1.day) }
+ scope :to, Proc.new { where('written_on <= ?', Time.now) }
+ end
+ assert_equal klazz.to.since.all, klazz.since.to.all
+ end
+
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
assert Topic.approved.respond_to?(:limit)
assert Topic.approved.respond_to?(:count)
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index a712e5f689..08f655d7fa 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -151,16 +151,11 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_does_not_wrap_string_results_in_arrays
- if current_adapter?(:SQLite3Adapter)
- require 'sqlite3/version'
- sqlite3_version = RUBY_PLATFORM =~ /java/ ? Jdbc::SQLite3::VERSION : SQLite3::VERSION
- end
-
Task.cache do
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter) && sqlite3_version > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:SQLite3Adapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index d5c0b351aa..33cd6020a1 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -88,6 +88,25 @@ class TightDescendant < TightPerson; end
class RichPerson < ActiveRecord::Base
self.table_name = 'people'
-
+
has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
end
+
+class NestedPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ attr_accessible :first_name, :best_friend_first_name, :best_friend_attributes
+ attr_accessible :first_name, :gender, :comments, :as => :admin
+ attr_accessible :best_friend_attributes, :best_friend_first_name, :as => :admin
+
+ has_one :best_friend, :class_name => 'NestedPerson', :foreign_key => :best_friend_id
+ accepts_nested_attributes_for :best_friend, :update_only => true
+
+ def comments=(new_comments)
+ raise RuntimeError
+ end
+
+ def best_friend_first_name=(new_name)
+ assign_attributes({ :best_friend_attributes => { :first_name => new_name } })
+ end
+end \ No newline at end of file
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index c778647ea5..804336da91 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Remove obsolete and unused `require_association` method from dependencies. *fxn*
+
* Add `:instance_accessor` option for `config_accessor`.
class User
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 8bdfa0c5bc..fe24f3716d 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -6,12 +6,14 @@ class LoadError
/^cannot load such file -- (.+)$/i,
]
- def path
- @path ||= begin
- REGEXPS.find do |regex|
- message =~ regex
+ unless method_defined?(:path)
+ def path
+ @path ||= begin
+ REGEXPS.find do |regex|
+ message =~ regex
+ end
+ $1
end
- $1
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 66f3af7002..5db06e0a56 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -168,8 +168,6 @@ module ActiveSupport #:nodoc:
end
end
- # Use const_missing to autoload associations so we don't have to
- # require_association when using single-table inheritance.
def const_missing(const_name, nesting = nil)
klass_name = name.presence || "Object"
@@ -220,11 +218,7 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
end
- Dependencies.depend_on(file_name, false, message)
- end
-
- def require_association(file_name)
- Dependencies.associate_with(file_name)
+ Dependencies.depend_on(file_name, message)
end
def load_dependency(file)
@@ -306,20 +300,14 @@ module ActiveSupport #:nodoc:
mechanism == :load
end
- def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb")
+ def depend_on(file_name, message = "No such file to load -- %s.rb")
path = search_for_file(file_name)
require_or_load(path || file_name)
rescue LoadError => load_error
- unless swallow_load_errors
- if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
- raise LoadError.new(message % file_name).copy_blame!(load_error)
- end
- raise
+ if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
+ raise LoadError.new(message % file_name).copy_blame!(load_error)
end
- end
-
- def associate_with(file_name)
- depend_on(file_name, true)
+ raise
end
def clear
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 8860636168..48c39d9370 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -35,22 +35,11 @@ module ActiveSupport
# have directories as keys and the value is an array of extensions to be
# watched under that directory.
#
- # This method must also receive a block that will be called once a path changes.
- #
- # == Implementation details
- #
- # This particular implementation checks for added, updated, and removed
- # files. Directories lookup are compiled to a glob for performance.
- # Therefore, while someone can add new files to the +files+ array after
- # initialization (and parts of Rails do depend on this feature), adding
- # new directories after initialization is not supported.
- #
- # Notice that other objects that implement the FileUpdateChecker API may
- # not even allow new files to be added after initialization. If this
- # is the case, we recommend freezing the +files+ after initialization to
- # avoid changes that won't make effect.
+ # This method must also receive a block that will be called once a path
+ # changes. The array of files and list of directories cannot be changed
+ # after FileUpdateChecker has been initialized.
def initialize(files, dirs={}, &block)
- @files = files
+ @files = files.freeze
@glob = compile_glob(dirs)
@block = block
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index bbeb8d82c6..f67b221024 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -9,25 +9,6 @@ module I18n
config.i18n.load_path = []
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
- def self.reloader
- @reloader ||= ActiveSupport::FileUpdateChecker.new(reloader_paths){ I18n.reload! }
- end
-
- def self.reloader_paths
- @reloader_paths ||= []
- end
-
- # Add <tt>I18n::Railtie.reloader</tt> to ActionDispatch callbacks. Since, at this
- # point, no path was added to the reloader, I18n.reload! is not triggered
- # on to_prepare callbacks. This will only happen on the config.after_initialize
- # callback below.
- initializer "i18n.callbacks" do |app|
- app.reloaders << I18n::Railtie.reloader
- ActionDispatch::Reloader.to_prepare do
- I18n::Railtie.reloader.execute_if_updated
- end
- end
-
# Set the i18n configuration after initialization since a lot of
# configuration is still usually done in application initializers.
config.after_initialize do |app|
@@ -63,7 +44,9 @@ module I18n
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
- reloader_paths.concat I18n.load_path
+ reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ app.reloaders << reloader
+ ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
reloader.execute
@i18n_inited = true
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 87b1d76026..47336d2143 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -133,7 +133,7 @@ module ActiveSupport #:nodoc:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize # => "日本語"
def titleize
- chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.upcase($1)})
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
end
alias_method :titlecase, :titleize
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 081e6a16fd..f622c6b43f 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -60,10 +60,6 @@ class DependenciesTest < ActiveSupport::TestCase
assert_raise(MissingSourceFile) { require_dependency("missing_service") }
end
- def test_missing_association_raises_nothing
- assert_nothing_raised { require_association("missing_model") }
- end
-
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
with_loading do
filename = 'dependencies/raises_exception'
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 11506554a9..2473cec384 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -15,68 +15,64 @@ class AssertDifferenceTest < ActiveSupport::TestCase
@object.num = 0
end
- if lambda { }.respond_to?(:binding)
- def test_assert_no_difference
- assert_no_difference '@object.num' do
- # ...
- end
+ def test_assert_no_difference
+ assert_no_difference '@object.num' do
+ # ...
end
+ end
- def test_assert_difference
- assert_difference '@object.num', +1 do
- @object.increment
- end
+ def test_assert_difference
+ assert_difference '@object.num', +1 do
+ @object.increment
end
+ end
- def test_assert_difference_with_implicit_difference
- assert_difference '@object.num' do
- @object.increment
- end
+ def test_assert_difference_with_implicit_difference
+ assert_difference '@object.num' do
+ @object.increment
end
+ end
- def test_arbitrary_expression
- assert_difference '@object.num + 1', +2 do
- @object.increment
- @object.increment
- end
+ def test_arbitrary_expression
+ assert_difference '@object.num + 1', +2 do
+ @object.increment
+ @object.increment
end
+ end
- def test_negative_differences
- assert_difference '@object.num', -1 do
- @object.decrement
- end
+ def test_negative_differences
+ assert_difference '@object.num', -1 do
+ @object.decrement
end
+ end
- def test_expression_is_evaluated_in_the_appropriate_scope
- silence_warnings do
- local_scope = local_scope = 'foo'
- assert_difference('local_scope; @object.num') { @object.increment }
- end
+ def test_expression_is_evaluated_in_the_appropriate_scope
+ silence_warnings do
+ local_scope = local_scope = 'foo'
+ assert_difference('local_scope; @object.num') { @object.increment }
end
+ end
- def test_array_of_expressions
- assert_difference [ '@object.num', '@object.num + 1' ], +1 do
- @object.increment
- end
+ def test_array_of_expressions
+ assert_difference [ '@object.num', '@object.num + 1' ], +1 do
+ @object.increment
end
+ end
- def test_array_of_expressions_identify_failure
- assert_raises(MiniTest::Assertion) do
- assert_difference ['@object.num', '1 + 1'] do
- @object.increment
- end
+ def test_array_of_expressions_identify_failure
+ assert_raises(MiniTest::Assertion) do
+ assert_difference ['@object.num', '1 + 1'] do
+ @object.increment
end
end
+ end
- def test_array_of_expressions_identify_failure_when_message_provided
- assert_raises(MiniTest::Assertion) do
- assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
- @object.increment
- end
+ def test_array_of_expressions_identify_failure_when_message_provided
+ assert_raises(MiniTest::Assertion) do
+ assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
+ @object.increment
end
end
- else
- def default_test; end
end
end
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index acf75d41cd..92c607b6f5 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -190,6 +190,8 @@ $ rake postgresql:build_databases
NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation.
+NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
+
If you’re using another database, check the files under +activerecord/test/connections+ for default connection information. You can edit these files to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
You can now run the tests as you did for +sqlite3+. The tasks are respectively
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index a6abe5ee97..e19aa68407 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman*
+
* Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj*
* Add `/rails/info/routes` path, displays same information as `rake routes` *Richard Schneeman & Andrew White*
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 24bcec854c..01f9396403 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -26,6 +26,9 @@
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL).
config.active_record.auto_explain_threshold_in_seconds = 0.5
+
+ # Raise an error on page load if there are pending migrations
+ config.active_record.migration_error = :page_load
<%- end -%>
<%- unless options.skip_sprockets? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index 276c8c1c6a..3d875c342e 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -22,5 +22,6 @@
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
+ <p>If you are the application owner check the logs for more information.</p>
</body>
</html>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index dfdb7d0b05..012977d3d2 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -21,5 +21,6 @@
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
</div>
+ <p>If you are the application owner check the logs for more information.</p>
</body>
</html>
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index a8f7aeac7d..0090293200 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -4,6 +4,8 @@ require 'rails/test_help'
class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
+ ActiveRecord::Migration.check_pending!
+
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index 82ffeebb86..568ed653b7 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -22,6 +22,8 @@ Gem::Specification.new do |s|
<% if full? && !options[:skip_javascript] -%>
# s.add_dependency "<%= "#{options[:javascript]}-rails" %>"
<% end -%>
+<% unless options[:skip_active_record] -%>
s.add_development_dependency "<%= gem_for_database %>"
+<% end -%>
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 743036362e..1369140537 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
-<% if full? && !options[:skip_active_record] -%>
+<% if full? && !options[:skip_active_record] && !options[:skip_test_unit] -%>
APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
<% end %>
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 206ce39773..f9cff5b627 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -20,7 +20,7 @@ namespace :rails do
project_templates = "#{Rails.root}/lib/templates"
default_templates = { "erb" => %w{controller mailer scaffold},
- "rails" => %w{controller helper scaffold_controller stylesheets} }
+ "rails" => %w{controller helper scaffold_controller assets} }
default_templates.each do |type, names|
local_template_type_dir = File.join(project_templates, type)
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 55bebb549b..0de4afe905 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -87,7 +87,7 @@ namespace :test do
if File.directory?(".svn")
changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] }
elsif File.directory?(".git")
- changed_since_checkin = silence_stderr { `git ls-files --modified --others` }.split.map { |path| path.chomp }
+ changed_since_checkin = silence_stderr { `git ls-files --modified --others --exclude-standard` }.split.map { |path| path.chomp }
else
abort "Not a Subversion or Git checkout."
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index d7689863e6..7193dfdef5 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -41,6 +41,21 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ test "a renders exception on pending migration" do
+ add_to_config <<-RUBY
+ config.active_record.migration_error = :page_load
+ config.consider_all_requests_local = true
+ config.action_dispatch.show_exceptions = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+ ActiveRecord::Migrator.stubs(:needs_migration?).returns(true)
+
+ get "/foo"
+ assert_equal 500, last_response.status
+ assert_match "ActiveRecord::PendingMigrationError", last_response.body
+ end
+
test "multiple queue construction is possible" do
require 'rails'
require "#{app_path}/config/environment"
@@ -361,7 +376,7 @@ module ApplicationTests
assert_equal ActiveModel::MassAssignmentSecurity::WhiteList,
ActiveRecord::Base.active_authorizers[:default].class
- assert_equal [""], ActiveRecord::Base.active_authorizers[:default].to_a
+ assert_equal [], ActiveRecord::Base.active_authorizers[:default].to_a
end
test "registers interceptors with ActionMailer" do
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 8cf867da3c..eac471a07e 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -190,5 +190,16 @@ module ApplicationTests
end
end
+ def test_copy_templates
+ Dir.chdir(app_path) do
+ `bundle exec rake rails:templates:copy`
+ %w(controller mailer scaffold).each do |dir|
+ assert File.exists?(File.join(app_path, 'lib', 'templates', 'erb', dir))
+ end
+ %w(controller helper scaffold_controller assets).each do |dir|
+ assert File.exists?(File.join(app_path, 'lib', 'templates', 'rails', dir))
+ end
+ end
+ end
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index f7dffe9a5c..0a235b56d5 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -58,6 +58,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/
end
+ def test_generating_test_files_in_full_mode_without_unit_test_files
+ run_generator [destination_root, "-T", "--full"]
+
+ assert_no_directory "test/integration/"
+ assert_no_file "test"
+ assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
+ end
+
def test_ensure_that_plugin_options_are_not_passed_to_app_generator
FileUtils.cd(Rails.root)
assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]))
@@ -82,6 +90,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "bukkits.gemspec", /mysql/
end
+ def test_dont_generate_development_dependency
+ run_generator [destination_root, "--skip-active-record"]
+
+ assert_file "bukkits.gemspec" do |contents|
+ assert_no_match(/s.add_development_dependency "sqlite3"/, contents)
+ end
+ end
+
def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
@@ -329,12 +345,10 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
protected
-
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
end
-protected
def default_files
::DEFAULT_PLUGIN_FILES
end