diff options
Diffstat (limited to 'activerecord')
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", |