aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile8
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/action_controller/test_case.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb4
-rw-r--r--actionpack/lib/action_view/template.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb1
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb35
-rw-r--r--activemodel/CHANGELOG.md25
-rw-r--r--activemodel/lib/active_model/secure_password.rb10
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb6
-rw-r--r--activemodel/test/cases/secure_password_test.rb6
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb31
-rw-r--r--activerecord/CHANGELOG.md108
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/inheritance.rb3
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb44
-rw-r--r--activerecord/lib/active_record/railties/console_sandbox.rb4
-rw-r--r--activerecord/lib/active_record/scoping/named.rb15
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb5
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb5
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb86
-rw-r--r--activerecord/test/cases/base_test.rb36
-rw-r--r--activerecord/test/cases/migration/logger_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb58
-rw-r--r--activerecord/test/cases/named_scope_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb7
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb7
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb13
-rw-r--r--activerecord/test/fixtures/traffic_lights.yml4
-rw-r--r--activerecord/test/models/traffic_light.rb1
-rw-r--r--activerecord/test/schema/schema.rb3
-rw-r--r--activesupport/CHANGELOG.md15
-rw-r--r--guides/code/getting_started/app/controllers/comments_controller.rb4
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb4
-rw-r--r--guides/source/migrations.md6
-rw-r--r--railties/lib/rails/commands/console.rb4
-rw-r--r--railties/test/application/console_test.rb82
48 files changed, 608 insertions, 143 deletions
diff --git a/Gemfile b/Gemfile
index 1e3d746a9b..d8b3390f87 100644
--- a/Gemfile
+++ b/Gemfile
@@ -30,7 +30,7 @@ instance_eval File.read local_gemfile if File.exists? local_gemfile
platforms :mri do
group :test do
gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0'
- gem 'debugger' if !ENV['TRAVIS'] && RUBY_VERSION < '2.0'
+ gem 'debugger' if !ENV['TRAVIS']
end
end
@@ -53,7 +53,7 @@ end
platforms :jruby do
gem 'json'
- gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.0'
+ gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.7'
# This is needed by now to let tests work on JRuby
# TODO: When the JRuby guys merge jruby-openssl in
@@ -61,8 +61,8 @@ platforms :jruby do
gem 'jruby-openssl'
group :db do
- gem 'activerecord-jdbcmysql-adapter', '>= 1.2.0'
- gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.0'
+ gem 'activerecord-jdbcmysql-adapter', '>= 1.2.7'
+ gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.7'
end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 3fc3e06160..1050626884 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* Fixed regression when using `assert_template` to verify files sent using
+ `render file: 'README.md'`.
+ Fixes #9464.
+
+ *Justin Coyne*
+
* Fixed `ActionView::Helpers::CaptureHelper#content_for` regression when trying to use it in
a boolean statement.
Fixes #9360.
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index bba1f1e201..e12bf0a1c6 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -16,6 +16,7 @@ module ActionController
@_partials = Hash.new(0)
@_templates = Hash.new(0)
@_layouts = Hash.new(0)
+ @_files = Hash.new(0)
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
@@ -39,6 +40,16 @@ module ActionController
@_templates[path] += 1
end
+
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ path = payload[:identifier]
+ next if payload[:virtual_path] # files don't have virtual path
+ if path
+ @_files[path] += 1
+ @_files[path.split("/").last] += 1
+ end
+
+ end
end
def teardown_subscriptions
@@ -106,7 +117,7 @@ module ActionController
end
assert matches_template, msg
when Hash
- options.assert_valid_keys(:layout, :partial, :locals, :count)
+ options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
if options.key?(:layout)
expected_layout = options[:layout]
@@ -123,6 +134,10 @@ module ActionController
end
end
+ if options[:file]
+ assert_includes @_files.keys, options[:file]
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
if defined?(@_rendered_views)
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 6b1f233930..550f4dbd0d 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -10,7 +10,7 @@
clean_params.delete("action")
clean_params.delete("controller")
- request_dump = clean_params.empty? ? 'None' : clean_params.inspect.tr(',', ",\n")
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
def debug_hash(object)
object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
@@ -31,4 +31,4 @@
</div>
<h2 style="margin-top: 30px">Response</h2>
-<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.tr(',', ",\n") : 'None' %></pre>
+<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index f73d14c79b..720890eeb9 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -138,7 +138,7 @@ module ActionView
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
- ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
+ ActiveSupport::Notifications.instrument("!render_template.action_view", virtual_path: @virtual_path, identifier: @identifier) do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 1a1083bf00..47bd011d5e 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -109,7 +109,7 @@ module ActionView
@cache.clear
end
- # Normalizes the arguments and passes it on to find_template.
+ # Normalizes the arguments and passes it on to find_templates.
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
cached(key, [name, prefix, partial], details, locals) do
find_templates(name, prefix, partial, details)
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 463f192d0c..10b487f37a 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -219,6 +219,7 @@ module ActionView
:@_routes,
:@controller,
:@_layouts,
+ :@_files,
:@_rendered_views,
:@method_name,
:@output_buffer,
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 5d727b3811..22a410db94 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -96,6 +96,14 @@ class ActionPackAssertionsController < ActionController::Base
raise "post" if request.post?
render :text => "request method: #{request.env['REQUEST_METHOD']}"
end
+
+ def render_file_absolute_path
+ render :file => File.expand_path('../../../README.rdoc', __FILE__)
+ end
+
+ def render_file_relative_path
+ render :file => 'README.rdoc'
+ end
end
# Used to test that assert_response includes the exception message
@@ -142,6 +150,16 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert_tag :content => "/action_pack_assertions/flash_me"
end
+ def test_render_file_absolute_path
+ get :render_file_absolute_path
+ assert_match(/\A= Action Pack/, @response.body)
+ end
+
+ def test_render_file_relative_path
+ get :render_file_relative_path
+ assert_match(/\A= Action Pack/, @response.body)
+ end
+
def test_get_request
assert_raise(RuntimeError) { get :raise_exception_on_get }
get :raise_exception_on_post
@@ -441,6 +459,23 @@ class AssertTemplateTest < ActionController::TestCase
assert_template :partial => '_partial'
end
+ def test_file_with_absolute_path_success
+ get :render_file_absolute_path
+ assert_template :file => File.expand_path('../../../README.rdoc', __FILE__)
+ end
+
+ def test_file_with_relative_path_success
+ get :render_file_relative_path
+ assert_template :file => 'README.rdoc'
+ end
+
+ def test_with_file_failure
+ get :render_file_absolute_path
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :file => 'test/hello_world'
+ end
+ end
+
def test_with_nil_passes_when_no_template_rendered
get :nothing
assert_template nil
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 1fe6dbd4d9..8c54ec3d45 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,30 @@
## Rails 4.0.0 (unreleased) ##
+* `has_secure_password` does not fail the confirmation validation
+ when assigning empty String to `password` and `password_confirmation`.
+
+ Example:
+
+ # given User has_secure_password.
+ @user.password = ""
+ @user.password_confirmation = ""
+ @user.valid?(:update) # used to be false
+
+* `validates_confirmation_of` does not override writer methods for
+ the confirmation attribute if no reader is defined.
+
+ Example:
+
+ class Blog
+ def title=(new_title)
+ @title = new_title.downcase
+ end
+
+ # previously this would override the setter above.
+ validates_confirmation_of :title
+ end
+
+ *Yves Senn*
## Rails 4.0.0.beta1 (February 25, 2013) ##
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 6644b60609..9324a1ad0a 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -48,6 +48,8 @@ module ActiveModel
attr_reader :password
+ include InstanceMethodsOnActivation
+
if options.fetch(:validations, true)
validates_confirmation_of :password
validates_presence_of :password, :on => :create
@@ -55,8 +57,6 @@ module ActiveModel
before_create { raise "Password digest missing on new record" if password_digest.blank? }
end
- include InstanceMethodsOnActivation
-
if respond_to?(:attributes_protected_by_default)
def self.attributes_protected_by_default #:nodoc:
super + ['password_digest']
@@ -99,6 +99,12 @@ module ActiveModel
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
end
end
+
+ def password_confirmation=(unencrypted_password)
+ unless unencrypted_password.blank?
+ @password_confirmation = unencrypted_password
+ end
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 3a3abce364..d14fb4dc53 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -10,9 +10,13 @@ module ActiveModel
end
def setup(klass)
- klass.send(:attr_accessor, *attributes.map do |attribute|
+ klass.send(:attr_reader, *attributes.map do |attribute|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
end.compact)
+
+ klass.send(:attr_writer, *attributes.map do |attribute|
+ :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
+ end.compact)
end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 7783bb25d5..02cd3b8a93 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -88,4 +88,10 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.password = "secret"
assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost
end
+
+ test "blank password_confirmation does not result in a confirmation error" do
+ @user.password = ""
+ @user.password_confirmation = ""
+ assert @user.valid?(:update), "user should be valid"
+ end
end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index f7556a249f..814eec3f59 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -71,4 +71,35 @@ class ConfirmationValidationTest < ActiveModel::TestCase
I18n.backend = @old_backend
end
+ test "does not override confirmation reader if present" do
+ klass = Class.new do
+ include ActiveModel::Validations
+
+ def title_confirmation
+ "expected title"
+ end
+
+ validates_confirmation_of :title
+ end
+
+ assert_equal "expected title", klass.new.title_confirmation,
+ "confirmation validation should not override the reader"
+ end
+
+ test "does not override confirmation writer if present" do
+ klass = Class.new do
+ include ActiveModel::Validations
+
+ def title_confirmation=(value)
+ @title_confirmation = "expected title"
+ end
+
+ validates_confirmation_of :title
+ end
+
+ model = klass.new
+ model.title_confirmation = "new title"
+ assert_equal "expected title", model.title_confirmation,
+ "confirmation validation should not override the writer"
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 97e81de1ca..d1e3488c1d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -6,6 +6,114 @@
*Dmitry Vorotilin*
+* When `:name` option is provided to `remove_index`, use it if there is no
+ index by the conventional name.
+
+ For example, previously if an index was removed like so
+ `remove_index :values, column: :value, name: 'a_different_name'`
+ the generated SQL would not contain the specified index name,
+ and hence the migration would fail.
+ Fixes #8858.
+
+ *Ezekiel Smithburg*
+
+* Created block to by-pass the prepared statement bindings.
+ This will allow to compose fragments of large SQL statements to
+ avoid multiple round-trips between Ruby and the DB.
+
+ Example:
+
+ sql = Post.connection.unprepared_statement do
+ Post.first.comments.to_sql
+ end
+
+ *Cédric Fabianski*
+
+* Change the semantics of combining scopes to be the same as combining
+ class methods which return scopes. For example:
+
+ class User < ActiveRecord::Base
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+ end
+
+ class Post < ActiveRecord::Base
+ def self.active
+ where state: 'active'
+ end
+
+ def self.inactive
+ where state: 'inactive'
+ end
+ end
+
+ ### BEFORE ###
+
+ User.where(state: 'active').where(state: 'inactive')
+ # => SELECT * FROM users WHERE state = 'active' AND state = 'inactive'
+
+ User.active.inactive
+ # => SELECT * FROM users WHERE state = 'inactive'
+
+ Post.active.inactive
+ # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
+
+ ### AFTER ###
+
+ User.active.inactive
+ # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
+
+ Before this change, invoking a scope would merge it into the current
+ scope and return the result. `Relation#merge` applies "last where
+ wins" logic to de-duplicate the conditions, but this lead to
+ confusing and inconsistent behaviour. This fixes that.
+
+ If you really do want the "last where wins" logic, you can opt-in to
+ it like so:
+
+ User.active.merge(User.inactive)
+
+ Fixes #7365.
+
+ *Neeraj Singh* and *Jon Leighton*
+
+* Expand `#cache_key` to consult all relevant updated timestamps.
+
+ Previously only `updated_at` column was checked, now it will
+ consult other columns that received updated timestamps on save,
+ such as `updated_on`. When multiple columns are present it will
+ use the most recent timestamp.
+ Fixes #9033.
+
+ *Brendon Murphy*
+
+* Throw `NotImplementedError` when trying to instantiate `ActiveRecord::Base` or an abstract class.
+
+ *Aaron Weiner*
+
+* Warn when `rake db:structure:dump` with a mysl database and
+ `mysqldump` is not in the PATH or fails.
+ Fixes #9518.
+
+ *Yves Senn*
+
+* Remove `connection#structure_dump`, which is no longer used. *Yves Senn*
+
+* Make it possible to execute migrations without a transaction even
+ if the database adapter supports DDL transactions.
+ Fixes #9483.
+
+ Example:
+
+ class ChangeEnum < ActiveRecord::Migration
+ self.disable_ddl_transaction!
+ def up
+ execute "ALTER TYPE model_size ADD VALUE 'new_value'"
+ end
+ end
+
+ *Yves Senn*
+
* Assigning "0.0" to a nullable numeric column does not make it dirty.
Fix #9034.
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 c3d15ca929..c64b542286 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -125,7 +125,8 @@ module ActiveRecord
# In order to get around this problem, #transaction will emulate the effect
# of nested transactions, by using savepoints:
# http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
- # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
+ # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
+ # supports savepoints.
#
# It is safe to call this method if a database transaction is already open,
# i.e. if #transaction is called within another #transaction block. In case
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 0cce8c7596..9d624a6648 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -516,11 +516,6 @@ module ActiveRecord
end
alias :remove_belongs_to :remove_reference
- # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
- # entire structure of the database.
- def structure_dump
- end
-
def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
@@ -692,6 +687,12 @@ module ActiveRecord
index_name = index_name(table_name, options)
unless index_name_exists?(table_name, index_name, true)
+ if options.has_key? :name
+ options_without_column = options.dup
+ options_without_column.delete :column
+ index_name_without_column = index_name(table_name, options_without_column)
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
+ end
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ff9de712bc..7949bcb5ce 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -118,6 +118,17 @@ module ActiveRecord
@in_use = false
end
+ def unprepared_visitor
+ self.class::BindSubstitution.new self
+ end
+
+ def unprepared_statement
+ old, @visitor = @visitor, unprepared_visitor
+ yield
+ ensure
+ @visitor = old
+ end
+
# Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_name
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 9826b18053..f88f5742a8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -143,7 +143,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::MySQL.new self
else
- @visitor = BindSubstitution.new self
+ @visitor = unprepared_visitor
end
end
@@ -332,20 +332,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def structure_dump #:nodoc:
- if supports_views?
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
- else
- sql = "SHOW TABLES"
- end
-
- select_all(sql, 'SCHEMA').map { |table|
- table.delete('Table_type')
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
- }.join
- end
-
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
def recreate_database(name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index c91e1b3fb9..691b2ab37f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -489,7 +489,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::PostgreSQL.new self
else
- @visitor = BindSubstitution.new self
+ @visitor = unprepared_visitor
end
@connection_parameters, @config = connection_parameters, config
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 981c4c96a0..84fa1c7d5a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -113,7 +113,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::SQLite.new self
else
- @visitor = BindSubstitution.new self
+ @visitor = unprepared_visitor
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index e4586f74fc..faef5a9527 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -15,6 +15,9 @@ module ActiveRecord
# and if the inheritance column is attr accessible, it initializes an
# instance of the given subclass instead of the base class
def new(*args, &block)
+ if abstract_class? || self == Base
+ raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
+ end
if (attrs = args.first).is_a?(Hash)
if subclass = subclass_from_attrs(attrs)
return subclass.new(*args, &block)
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 32d35f0ec1..48c73d7781 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -49,7 +49,7 @@ module ActiveRecord
case
when new_record?
"#{self.class.model_name.cache_key}/new"
- when timestamp = self[:updated_at]
+ when timestamp = max_updated_column_timestamp
timestamp = timestamp.utc.to_s(cache_timestamp_format)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 823595a128..62e8881c4c 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -330,6 +330,23 @@ module ActiveRecord
#
# For a list of commands that are reversible, please see
# <tt>ActiveRecord::Migration::CommandRecorder</tt>.
+ #
+ # == Transactional Migrations
+ #
+ # If the database adapter supports DDL transactions, all migrations will
+ # automatically be wrapped in a transaction. There are queries that you
+ # can't execute inside a transaction though, and for these situations
+ # you can turn the automatic transactions off.
+ #
+ # class ChangeEnum < ActiveRecord::Migration
+ # self.disable_ddl_transaction!
+ # def up
+ # execute "ALTER TYPE model_size ADD VALUE 'new_value'"
+ # end
+ # end
+ #
+ # Remember that you can still open your own transactions, even if you
+ # are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
@@ -351,6 +368,7 @@ module ActiveRecord
class << self
attr_accessor :delegate # :nodoc:
+ attr_accessor :disable_ddl_transaction # :nodoc:
end
def self.check_pending!
@@ -365,8 +383,16 @@ module ActiveRecord
new.migrate direction
end
- cattr_accessor :verbose
+ # Disable DDL transactions for this migration.
+ def self.disable_ddl_transaction!
+ @disable_ddl_transaction = true
+ end
+
+ def disable_ddl_transaction # :nodoc:
+ self.class.disable_ddl_transaction
+ end
+ cattr_accessor :verbose
attr_accessor :name, :version
def initialize(name = self.class.name, version = nil)
@@ -375,8 +401,8 @@ module ActiveRecord
@connection = nil
end
+ self.verbose = true
# instantiate the delegate object after initialize is defined
- self.verbose = true
self.delegate = new
# Reverses the migration commands for the given block and
@@ -663,7 +689,7 @@ module ActiveRecord
File.basename(filename)
end
- delegate :migrate, :announce, :write, :to => :migration
+ delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
private
@@ -856,12 +882,12 @@ module ActiveRecord
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
begin
- ddl_transaction do
+ ddl_transaction(migration) do
migration.migrate(@direction)
record_version_state_after_migrating(migration.version)
end
rescue => e
- canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
+ canceled_msg = use_transaction?(migration) ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
end
end
@@ -935,12 +961,16 @@ module ActiveRecord
end
# Wrap the migration in a transaction only if supported by the adapter.
- def ddl_transaction
- if Base.connection.supports_ddl_transactions?
+ def ddl_transaction(migration)
+ if use_transaction?(migration)
Base.transaction { yield }
else
yield
end
end
+
+ def use_transaction?(migration)
+ !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
index 90b462fad6..25a591aa43 100644
--- a/activerecord/lib/active_record/railties/console_sandbox.rb
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
@@ -1,4 +1,4 @@
-ActiveRecord::Base.connection.begin_db_transaction
+ActiveRecord::Base.connection.begin_transaction
at_exit do
- ActiveRecord::Base.connection.rollback_db_transaction
+ ActiveRecord::Base.connection.rollback_transaction
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 01fbb96b8e..12317601b6 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -159,10 +159,19 @@ module ActiveRecord
end
singleton_class.send(:define_method, name) do |*args|
- options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
- relation = all.merge(options)
+ if body.respond_to?(:call)
+ scope = extension ? body.call(*args).extending(extension) : body.call(*args)
- extension ? relation.extending(extension) : relation
+ if scope
+ default_scoped = scope.default_scoped
+ scope = relation.merge(scope)
+ scope.default_scoped = default_scoped
+ end
+ else
+ scope = body
+ end
+
+ scope || all
end
end
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 17378969a5..10696258c9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -57,7 +57,10 @@ module ActiveRecord
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
args.concat(["#{configuration['database']}"])
- Kernel.system(*args)
+ unless Kernel.system(*args)
+ $stderr.puts "Could not dump the database structure. "\
+ "Make sure `mysqldump` is in your PATH and check the command output for warnings."
+ end
end
def structure_load(filename)
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 8ded6d4a86..ae99cff35e 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -98,6 +98,12 @@ module ActiveRecord
timestamp_attributes_for_create + timestamp_attributes_for_update
end
+ def max_updated_column_timestamp
+ if (timestamps = timestamp_attributes_for_update.map { |attr| self[attr] }.compact).present?
+ timestamps.map { |ts| ts.to_time }.max
+ end
+ end
+
def current_time_from_proper_timezone
self.class.default_timezone == :utc ? Time.now.utc : Time.now
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 5164acf77f..4cf4bc4c61 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -63,11 +63,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
end
- # dump structure of table with reserved word name
- def test_structure_dump
- assert_nothing_raised { @connection.structure_dump }
- end
-
# introspect table with reserved word name
def test_introspect
assert_nothing_raised { @connection.columns(:group) }
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 1265cb927e..fedd9f603c 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -70,12 +70,6 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
- def test_logs_name_structure_dump
- @connection.structure_dump
- assert_equal "SCHEMA", @connection.logged[0][1]
- assert_equal "SCHEMA", @connection.logged[2][1]
- end
-
def test_logs_name_show_variable
@connection.show_variable 'foo'
assert_equal "SCHEMA", @connection.logged[0][1]
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 1017b0758d..98596a7f62 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -63,11 +63,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
end
- # dump structure of table with reserved word name
- def test_structure_dump
- assert_nothing_raised { @connection.structure_dump }
- end
-
# introspect table with reserved word name
def test_introspect
assert_nothing_raised { @connection.columns(:group) }
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 3a6da0e59f..f5316952b8 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -21,9 +21,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
:posts, :tags, :taggings, :comments, :sponsors, :members
def test_belongs_to
- Client.find(3).firm.name
- assert_equal companies(:first_firm).name, Client.find(3).firm.name
- assert_not_nil Client.find(3).firm, "Microsoft should have a firm"
+ firm = Client.find(3).firm
+ assert_not_nil firm
+ assert_equal companies(:first_firm).name, firm.name
end
def test_belongs_to_with_primary_key
@@ -232,15 +232,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_counter_with_assigning_nil
- p = Post.find(1)
- c = Comment.find(1)
+ post = Post.find(1)
+ comment = Comment.find(1)
- assert_equal p.id, c.post_id
- assert_equal 2, Post.find(p.id).comments.size
+ assert_equal post.id, comment.post_id
+ assert_equal 2, Post.find(post.id).comments.size
- c.post = nil
+ comment.post = nil
- assert_equal 1, Post.find(p.id).comments.size
+ assert_equal 1, Post.find(post.id).comments.size
end
def test_belongs_to_with_primary_key_counter
@@ -263,56 +263,56 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_counter_with_reassigning
- t1 = Topic.create("title" => "t1")
- t2 = Topic.create("title" => "t2")
- r1 = Reply.new("title" => "r1", "content" => "r1")
- r1.topic = t1
+ topic1 = Topic.create("title" => "t1")
+ topic2 = Topic.create("title" => "t2")
+ reply1 = Reply.new("title" => "r1", "content" => "r1")
+ reply1.topic = topic1
- assert r1.save
- assert_equal 1, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 1, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
- r1.topic = Topic.find(t2.id)
+ reply1.topic = Topic.find(topic2.id)
assert_no_queries do
- r1.topic = t2
+ reply1.topic = topic2
end
- assert r1.save
- assert_equal 0, Topic.find(t1.id).replies.size
- assert_equal 1, Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 0, Topic.find(topic1.id).replies.size
+ assert_equal 1, Topic.find(topic2.id).replies.size
- r1.topic = nil
+ reply1.topic = nil
- assert_equal 0, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert_equal 0, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
- r1.topic = t1
+ reply1.topic = topic1
- assert_equal 1, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert_equal 1, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
- r1.destroy
+ reply1.destroy
- assert_equal 0, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert_equal 0, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
end
def test_belongs_to_reassign_with_namespaced_models_and_counters
- t1 = Web::Topic.create("title" => "t1")
- t2 = Web::Topic.create("title" => "t2")
- r1 = Web::Reply.new("title" => "r1", "content" => "r1")
- r1.topic = t1
+ topic1 = Web::Topic.create("title" => "t1")
+ topic2 = Web::Topic.create("title" => "t2")
+ reply1 = Web::Reply.new("title" => "r1", "content" => "r1")
+ reply1.topic = topic1
- assert r1.save
- assert_equal 1, Web::Topic.find(t1.id).replies.size
- assert_equal 0, Web::Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 1, Web::Topic.find(topic1.id).replies.size
+ assert_equal 0, Web::Topic.find(topic2.id).replies.size
- r1.topic = Web::Topic.find(t2.id)
+ reply1.topic = Web::Topic.find(topic2.id)
- assert r1.save
- assert_equal 0, Web::Topic.find(t1.id).replies.size
- assert_equal 1, Web::Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 0, Web::Topic.find(topic1.id).replies.size
+ assert_equal 1, Web::Topic.find(topic2.id).replies.size
end
def test_belongs_to_counter_after_save
@@ -367,9 +367,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_new_record_with_foreign_key_but_no_object
- c = Client.new("firm_id" => 1)
+ client = Client.new("firm_id" => 1)
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.all.merge!(:order => "id").first, c.firm_with_basic_id
+ assert_equal Firm.all.merge!(:order => "id").first, client.firm_with_basic_id
end
def test_setting_foreign_key_after_nil_target_loaded
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index af1845c937..e5880a6b7b 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -307,6 +307,20 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("last_read", ex.errors[0].attribute)
end
+ def test_initialize_abstract_class
+ e = assert_raises(NotImplementedError) do
+ FirstAbstractClass.new
+ end
+ assert_equal("FirstAbstractClass is an abstract class and can not be instantiated.", e.message)
+ end
+
+ def test_initialize_base
+ e = assert_raises(NotImplementedError) do
+ ActiveRecord::Base.new
+ end
+ assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message)
+ end
+
def test_create_after_initialize_without_block
cb = CustomBulb.create(:name => 'Dude')
assert_equal('Dude', cb.name)
@@ -1441,12 +1455,30 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal key, car.cache_key
end
- def test_cache_key_format_for_existing_record_with_nil_updated_at
+ def test_cache_key_format_for_existing_record_with_nil_updated_timestamps
dev = Developer.first
- dev.update_columns(updated_at: nil)
+ dev.update_columns(updated_at: nil, updated_on: nil)
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+ def test_cache_key_for_updated_on
+ dev = Developer.first
+ dev.updated_at = nil
+ assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_for_newer_updated_at
+ dev = Developer.first
+ dev.updated_at += 3600
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_for_newer_updated_on
+ dev = Developer.first
+ dev.updated_on += 3600
+ assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
+ end
+
def test_touch_should_raise_error_on_a_new_object
company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals")
assert_raises(ActiveRecord::ActiveRecordError) do
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index ee0c20747e..97efb94b66 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -7,6 +7,7 @@ module ActiveRecord
self.use_transactional_fixtures = false
Migration = Struct.new(:name, :version) do
+ def disable_ddl_transaction; false end
def migrate direction
# do nothing
end
@@ -34,4 +35,3 @@ module ActiveRecord
end
end
end
-
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index fa8dec0e15..6f90500189 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -239,9 +239,13 @@ class MigrationTest < ActiveRecord::TestCase
assert_not Person.column_methods_hash.include?(:last_name)
- migration = Struct.new(:name, :version) {
- def migrate(x); raise 'Something broke'; end
- }.new('zomg', 100)
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ raise 'Something broke'
+ end
+ }.new
migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
@@ -250,7 +254,39 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
Person.reset_column_information
+ assert_not Person.column_methods_hash.include?(:last_name),
+ "On error, the Migrator should revert schema changes but it did not."
+ end
+
+ def test_migration_without_transaction
+ unless ActiveRecord::Base.connection.supports_ddl_transactions?
+ skip "not supported on #{ActiveRecord::Base.connection.class}"
+ end
+
assert_not Person.column_methods_hash.include?(:last_name)
+
+ migration = Class.new(ActiveRecord::Migration) {
+ self.disable_ddl_transaction!
+
+ def version; 101 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ raise 'Something broke'
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 101)
+ e = assert_raise(StandardError) { migrator.migrate }
+ assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name),
+ "without ddl transactions, the Migrator should not rollback on error but it did."
+ ensure
+ Person.reset_column_information
+ if Person.column_methods_hash.include?(:last_name)
+ Person.connection.remove_column('people', 'last_name')
+ end
end
def test_schema_migrations_table_name
@@ -426,6 +462,22 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase
end
end
+class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase
+ def test_drop_index_by_name
+ connection = Person.connection
+ connection.create_table :values, force: true do |t|
+ t.integer :value
+ end
+
+ assert_nothing_raised ArgumentError do
+ connection.add_index :values, :value, name: 'a_different_name'
+ connection.remove_index :values, column: :value, name: 'a_different_name'
+ end
+
+ connection.drop_table :values rescue nil
+ end
+end
+
if ActiveRecord::Base.connection.supports_bulk_alter?
class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
def setup
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index bd121126e7..b593270352 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -309,7 +309,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size
end
- def test_chaining_should_use_latest_conditions_when_creating
+ def test_chaining_applies_last_conditions_when_creating
post = Topic.rejected.new
assert !post.approved?
@@ -323,13 +323,13 @@ class NamedScopeTest < ActiveRecord::TestCase
assert post.approved?
end
- def test_chaining_should_use_latest_conditions_when_searching
+ def test_chaining_combines_conditions_when_searching
# Normal hash conditions
- assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.to_a
- assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.to_a
+ assert_equal Topic.where(approved: false).where(approved: true).to_a, Topic.rejected.approved.to_a
+ assert_equal Topic.where(approved: true).where(approved: false).to_a, Topic.approved.rejected.to_a
# Nested hash conditions with same keys
- assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.to_a
+ assert_equal [], Post.with_special_comments.with_very_special_comments.to_a
# Nested hash conditions with different keys
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).to_a.uniq
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8156f99037..b936cca875 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -399,14 +399,6 @@ class PersistencesTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
end
- def test_string_ids
- # FIXME: Fix this failing test
- skip "Failing test. We need this fixed before 4.0.0"
- mv = Minivan.where(:minivan_id => 1234).first_or_initialize
- assert mv.new_record?
- assert_equal '1234', mv.minivan_id
- end
-
def test_update_attribute_with_one_updated
t = Topic.first
t.update_attribute(:title, 'super_title')
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 8298d7534c..26cbb03892 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -714,6 +714,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [developers(:poor_jamis)], dev_with_count.to_a
end
+ def test_relation_to_sql
+ sql = Post.connection.unprepared_statement do
+ Post.first.comments.to_sql
+ end
+ assert_no_match(/\?/, sql)
+ end
+
def test_relation_merging_with_arel_equalities_keeps_last_equality
devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge(
Developer.where(Developer.arel_table[:salary].eq(9000))
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 295c7e13fa..726338db14 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'models/topic'
require 'models/person'
+require 'models/traffic_light'
require 'bcrypt'
class SerializedAttributeTest < ActiveRecord::TestCase
@@ -234,4 +235,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
person = person.reload
assert_equal(insures, person.insures)
end
+
+ def test_regression_serialized_default_on_text_column_with_null_false
+ light = TrafficLight.new
+ assert_equal [], light.state
+ assert_equal [], light.long_state
+ end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 38b9dd02f0..dadcca5b7f 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -249,10 +249,21 @@ module ActiveRecord
def test_structure_dump
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db")
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
+
+ def test_warn_when_external_structure_dump_fails
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(false)
+
+ warnings = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+
+ assert_match(/Could not dump the database structure/, warnings)
+ end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
diff --git a/activerecord/test/fixtures/traffic_lights.yml b/activerecord/test/fixtures/traffic_lights.yml
index 6dabd53474..81b4e47959 100644
--- a/activerecord/test/fixtures/traffic_lights.yml
+++ b/activerecord/test/fixtures/traffic_lights.yml
@@ -4,3 +4,7 @@ uk:
- Green
- Red
- Orange
+ long_state:
+ - "Green, go ahead"
+ - "Red, wait"
+ - "Orange, caution light is about to switch" \ No newline at end of file
diff --git a/activerecord/test/models/traffic_light.rb b/activerecord/test/models/traffic_light.rb
index 228f3f7bd4..a6b7edb882 100644
--- a/activerecord/test/models/traffic_light.rb
+++ b/activerecord/test/models/traffic_light.rb
@@ -1,3 +1,4 @@
class TrafficLight < ActiveRecord::Base
serialize :state, Array
+ serialize :long_state, Array
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index cd9835259a..ae779c702a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -219,6 +219,8 @@ ActiveRecord::Schema.define do
t.integer :salary, :default => 70000
t.datetime :created_at
t.datetime :updated_at
+ t.datetime :created_on
+ t.datetime :updated_on
end
create_table :developers_projects, :force => true, :id => false do |t|
@@ -685,6 +687,7 @@ ActiveRecord::Schema.define do
create_table :traffic_lights, :force => true do |t|
t.string :location
t.string :state
+ t.text :long_state, :null => false
t.datetime :created_at
t.datetime :updated_at
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index c47cb75274..9d26b8ba3e 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,11 +1,24 @@
## Rails 4.0.0 (unreleased) ##
* Fix deletion of empty directories in ActiveSupport::Cache::FileStore.
-
+
*Charles Jones*
## Rails 4.0.0.beta1 (February 25, 2013) ##
+* Improve singularizing a singular for multiple cases.
+ Fixes #2608 #1825 #2395.
+
+ Example:
+
+ # Before
+ 'address'.singularize # => 'addres'
+
+ # After
+ 'address'.singularize # => 'address'
+
+ *Mark McSpadden*
+
* Prevent `DateTime#change` from truncating the second fraction, when seconds
do not need to be changed.
diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb
index 0082e9c8ec..0e3d2a6dde 100644
--- a/guides/code/getting_started/app/controllers/comments_controller.rb
+++ b/guides/code/getting_started/app/controllers/comments_controller.rb
@@ -1,7 +1,7 @@
class CommentsController < ApplicationController
-  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
-
+ http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
+
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment].permit(:commenter, :body))
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
index 0398395200..6aa1409170 100644
--- a/guides/code/getting_started/app/controllers/posts_controller.rb
+++ b/guides/code/getting_started/app/controllers/posts_controller.rb
@@ -1,7 +1,7 @@
class PostsController < ApplicationController
-  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
-
+ http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
+
def index
@posts = Post.all
end
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index 89ae564c24..bd63970bea 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -61,6 +61,10 @@ migrations are wrapped in a transaction. If the database does not support this
then when a migration fails the parts of it that succeeded will not be rolled
back. You will have to rollback the changes that were made by hand.
+NOTE: There are certain queries that can't run inside a transaction. If your
+adapter supports DDL transactions you can use `disable_ddl_transaction!` to
+disable them for a single migration.
+
If you wish for a migration to do something that Active Record doesn't know how
to reverse, you can use `reversible`:
@@ -180,7 +184,7 @@ end
```
If the migration name is of the form "CreateXXX" and is
-followed by a list of column names and types then a migration creating the table
+followed by a list of column names and types then a migration creating the table
XXX with the columns listed will be generated. For example:
```bash
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 86ab1aabbf..96229bb4f6 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -46,7 +46,10 @@ module Rails
def initialize(app, options={})
@app = app
@options = options
+
+ app.sandbox = sandbox?
app.load_console
+
@console = app.config.console || IRB
end
@@ -71,7 +74,6 @@ module Rails
end
def start
- app.sandbox = sandbox?
require_debugger if debugger?
set_environment! if environment?
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 3cb3643e3a..588682d3d4 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -81,18 +81,84 @@ class ConsoleTest < ActiveSupport::TestCase
assert_equal 'Once upon a time in a world...',
helper.truncate('Once upon a time in a world far far away')
end
+end
+
+begin
+ require "pty"
+rescue LoadError
+end
- def test_with_sandbox
- require 'rails/all'
- value = false
+class FullStackConsoleTest < ActiveSupport::TestCase
+ def setup
+ skip "PTY unavailable" unless defined?(PTY) && PTY.respond_to?(:open)
- Class.new(Rails::Railtie) do
- console do |app|
- value = app.sandbox?
+ build_app
+ app_file 'app/models/post.rb', <<-CODE
+ class Post < ActiveRecord::Base
+ end
+ CODE
+ system "#{app_path}/bin/rails runner 'Post.connection.create_table :posts'"
+
+ @master, @slave = PTY.open
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def assert_output(expected, timeout = 0.2)
+ timeout = Time.now + timeout
+
+ output = ""
+ until output.include?(expected) || Time.now > timeout
+ if IO.select([@master], [], [], 0.1)
+ output << @master.readpartial(100)
end
end
- load_environment(true)
- assert value
+ assert output.include?(expected), "#{expected.inspect} expected, but got:\n\n#{output}"
+ end
+
+ def write_prompt(command)
+ @master.puts command
+ assert_output command
+ end
+
+ def kill(pid)
+ Process.kill('QUIT', pid)
+ Process.wait(pid)
+ rescue Errno::ESRCH
+ end
+
+ def spawn_console
+ pid = Process.spawn(
+ "#{app_path}/bin/rails console --sandbox",
+ in: @slave, out: @slave, err: @slave
+ )
+
+ assert_output "> ", 5
+ pid
+ end
+
+ def test_sandbox
+ pid = spawn_console
+
+ write_prompt "Post.count"
+ assert_output "=> 0"
+
+ write_prompt "Post.create"
+ assert_output "=> "
+
+ write_prompt "Post.count"
+ assert_output "=> 1"
+
+ kill pid
+
+ pid = spawn_console
+
+ write_prompt "Post.count"
+ assert_output "=> 0"
+ ensure
+ kill pid
end
end