aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md20
-rw-r--r--activerecord/lib/active_record/associations.rb5
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb21
-rw-r--r--activerecord/lib/active_record/railties/databases.rake3
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb24
-rw-r--r--activerecord/test/cases/persistence_test.rb14
-rw-r--r--activerecord/test/cases/transactions_test.rb42
-rw-r--r--activerecord/test/models/developer.rb2
11 files changed, 143 insertions, 15 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4164b928bd..62f3e1eba2 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,23 @@
+* Fixed HABTM's CollectionAssociation size calculation.
+
+ HABTM should fall back to using the normal CollectionAssociation's size
+ calculation if the collection is not cached or loaded.
+
+ Fixes #14913 and #14914.
+
+ *Fred Wu*
+
+* Return a non zero status when running `rake db:migrate:status` and migration table does
+ not exist.
+
+ *Paul B.*
+
+* Keep track of dirty attributes after transaction is rollback.
+
+ Related #13166.
+
+ *Bogdan Gusiev* *arthurnn*
+
* Add support for module-level `table_name_suffix` in models.
This makes `table_name_suffix` work the same way as `table_name_prefix` when
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index ac1479ad8f..d66a79a137 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1576,6 +1576,11 @@ module ActiveRecord
join_model = builder.through_model
+ # FIXME: we should move this to the internal constants. Also people
+ # should never directly access this constant so I'm not happy about
+ # setting it.
+ const_set join_model.name, join_model
+
middle_reflection = builder.middle_reflection join_model
Builder::HasMany.define_callbacks self, middle_reflection
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index cdabf02b95..aeb77e2753 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -12,17 +12,18 @@ module ActiveRecord
@through_association = nil
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
- # SELECT query if you use #length.
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
+ # if the collection hasn't been loaded, and by calling collection.size if
+ # it has. If the collection will likely have a size greater than zero,
+ # and if fetching the collection will be needed afterwards, one less
+ # SELECT query will be generated by using #length instead.
def size
if has_cached_counter?
owner.read_attribute cached_counter_attribute_name(reflection)
elsif loaded?
target.size
else
- count
+ super
end
end
@@ -72,13 +73,11 @@ module ActiveRecord
@through_association ||= owner.association(through_reflection.name)
end
- # We temporarily cache through record that has been built, because if we build a
- # through record in build_record and then subsequently call insert_record, then we
- # want to use the exact same object.
+ # The through record (built with build_record) is temporarily cached
+ # so that it may be reused if insert_record is subsequently called.
#
- # However, after insert_record has been called, we clear the cache entry because
- # we want it to be possible to have multiple instances of the same record in an
- # association
+ # However, after insert_record has been called, the cache is cleared in
+ # order to allow multiple instances of the same record in an association.
def build_through_record(record)
@through_records[record.object_id] ||= begin
ensure_mutable
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 9538ead5f1..9e8e5fe94b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -83,8 +83,7 @@ db_namespace = namespace :db do
desc 'Display status of migrations'
task :status => [:environment, :load_config] do
unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
- puts 'Schema migrations table does not exist yet.'
- next # means "return" for rake task
+ abort 'Schema migrations table does not exist yet.'
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
db_list.map! { |version| "%.3d" % version }
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 17f76b63b3..e31d28cfd1 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -350,6 +350,7 @@ module ActiveRecord
end
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
@_start_transaction_state[:frozen?] = @attributes.frozen?
+ @_start_transaction_state[:changed_attributes] ||= changed_attributes.dup
end
# Clear the new record state and id of a record.
@@ -368,6 +369,9 @@ module ActiveRecord
@attributes = @attributes.dup if @attributes.frozen?
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
+ changed_attributes.replace(restore_state[:changed_attributes]).delete_if do |attribute, old_value|
+ old_value == @attributes[attribute]
+ end
if restore_state.has_key?(:id)
write_attribute(self.class.primary_key, restore_state[:id])
else
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index b7791078db..49f5ec250f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -396,6 +396,23 @@ module ActiveRecord
reset_connection
end
+ def test_unparsed_defaults_are_at_least_set_when_saving
+ with_example_table "id SERIAL PRIMARY KEY, number INTEGER NOT NULL DEFAULT (4 + 4) * 2 / 4" do
+ number_klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'ex'
+ end
+ column = number_klass.columns_hash["number"]
+ assert_nil column.default
+ assert_nil column.default_function
+
+ first_number = number_klass.new
+ assert_nil first_number.number
+
+ first_number.save!
+ assert_equal 4, first_number.reload.number
+ end
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index bdf8e15e3e..ffce7c0d5f 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -45,12 +45,14 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
UUIDType.reset_column_information
column = UUIDType.columns.find { |c| c.name == 'thingy' }
assert_equal "uuid_generate_v1()", column.default_function
-
+
@connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
-
+
UUIDType.reset_column_information
column = UUIDType.columns.find { |c| c.name == 'thingy' }
assert_equal "uuid_generate_v4()", column.default_function
+ ensure
+ UUIDType.reset_column_information
end
def test_data_type_of_uuid_types
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 5d33634da2..cfdfff6af9 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
@@ -83,6 +83,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
country.treaties << treaty
end
+ def test_marshal_dump
+ post = posts :welcome
+ preloaded = Post.includes(:categories).find post.id
+ assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
+ end
+
def test_should_property_quote_string_primary_keys
setup_data_for_habtm_case
@@ -217,6 +223,24 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers
end
+ def test_habtm_collection_size_from_build
+ devel = Developer.create("name" => "Fred Wu")
+ devel.projects << Project.create("name" => "Grimetime")
+ devel.projects.build
+
+ assert_equal 2, devel.projects.size
+ end
+
+ def test_habtm_collection_size_from_params
+ devel = Developer.new({
+ projects_attributes: {
+ '0' => {}
+ }
+ })
+
+ assert_equal 1, devel.projects.size
+ end
+
def test_build
devel = Developer.find(1)
proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 823146b399..5d963098fb 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/aircraft'
require 'models/post'
require 'models/comment'
require 'models/author'
@@ -829,4 +830,17 @@ class PersistenceTest < ActiveRecord::TestCase
end
end
+ def test_persist_inherited_class_with_different_table_name
+ minimalistic_aircrafts = Class.new(Minimalistic) do
+ self.table_name = "aircraft"
+ end
+
+ assert_difference "Aircraft.count", 1 do
+ aircraft = minimalistic_aircrafts.create(name: "Wright Flyer")
+ aircraft.name = "Wright Glider"
+ aircraft.save
+ end
+
+ assert_equal "Wright Glider", Aircraft.last.name
+ end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index e6ed85394b..7f2e830083 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -274,6 +274,48 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_rollback_when_changing_inside_transaction
+ assert !@first.approved?
+ Topic.transaction do
+ @first.approved = true
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+ assert @first.approved
+ assert @first.changes["approved"]
+ @first.save!
+ assert @first.reload.approved
+ end
+
+ def test_rollback_when_changing_outside_transaction
+ assert !@first.approved?
+ @first.approved = true
+ Topic.transaction do
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+ assert @first.changes["approved"]
+ assert @first.approved
+ @first.save!
+ assert @first.reload.approved
+ end
+
+ def test_rollback_when_changing_back_to_prev_stage
+ assert !@first.approved?
+ Topic.transaction do
+ @first.approved = true
+ @first.save!
+ @first.approved = false
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+ assert !@first.approved
+ assert !@first.changes["approved"]
+ @first.save!
+ assert !@first.reload.approved
+ end
+
+
def test_force_savepoint_in_nested_transaction
Topic.transaction do
@first.approved = true
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 762259ffa3..0a614c3bfd 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -13,6 +13,8 @@ class Developer < ActiveRecord::Base
end
end
+ accepts_nested_attributes_for :projects
+
has_and_belongs_to_many :projects_extended_by_name,
-> { extending(DeveloperProjectsAssociationExtension) },
:class_name => "Project",