diff options
Diffstat (limited to 'activerecord')
38 files changed, 311 insertions, 95 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f53cb6ad51..904ed0e362 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,44 @@ +* Assign inet/cidr attribute with `nil` value for invalid address. + + Example: + + record = User.new + + record.logged_in_from_ip # is type of an inet or a cidr + + # Before: + record.logged_in_from_ip = 'bad ip address' # raise exception + + # After: + record.logged_in_from_ip = 'bad ip address' # do not raise exception + record.logged_in_from_ip # => nil + record.logged_in_from_ip_before_type_cast # => 'bad ip address' + + *Paul Nikitochkin* + +* `add_to_target` now accepts a second optional `skip_callbacks` argument + + If truthy, it will skip the :before_add and :after_add callbacks. + + *Ben Woosley* + +* Fix interactions between `:before_add` callbacks and nested attributes + assignment of `has_many` associations, when the association was not + yet loaded: + + - A `:before_add` callback was being called when a nested attributes + assignment assigned to an existing record. + + - Nested Attributes assignment did not affect the record in the + association target when a `:before_add` callback triggered the + loading of the association + + *Jörg Schray* + +* Allow enable_extension migration method to be revertible. + + *Eric Tipton* + * Type cast hstore values on write, so that the value is consistent with reading from the database. diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 8ce02afef8..228c500f0a 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -290,7 +290,7 @@ module ActiveRecord # Returns true if the collection is empty. # - # If the collection has been loaded + # If the collection has been loaded # it is equivalent to <tt>collection.size.zero?</tt>. If the # collection has not been loaded, it is equivalent to # <tt>collection.exists?</tt>. If the collection has not already been @@ -366,8 +366,8 @@ module ActiveRecord target end - def add_to_target(record) - callback(:before_add, record) + def add_to_target(record, skip_callbacks = false) + callback(:before_add, record) unless skip_callbacks yield(record) if block_given? if association_scope.distinct_value && index = @target.index(record) @@ -376,7 +376,7 @@ module ActiveRecord @target << record end - callback(:after_add, record) + callback(:after_add, record) unless skip_callbacks set_inverse_instance(record) record 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 97e1bc5379..32244b1755 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -39,8 +39,8 @@ module ActiveRecord # Returns an array of the values of the first column in a select: # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] def select_values(arel, name = nil) - result = select_rows(to_sql(arel, []), name) - result.map { |v| v[0] } + select_rows(to_sql(arel, []), name) + .map { |v| v[0] } end # Returns an array of arrays containing the field values. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index d18b9c991f..c8fa4ef9af 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -15,7 +15,6 @@ module ActiveRecord return "'#{quote_string(value)}'" unless column case column.type - when :binary then "'#{quote_string(column.string_to_binary(value))}'" when :integer then value.to_i.to_s when :float then value.to_f.to_s else diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 0be4b5cb19..063b19871a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -16,9 +16,6 @@ module ActiveRecord # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc: - def string_to_binary(value) - value - end def primary_key? primary_key || type.to_sym == :primary_key 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 daa9d902fe..4b11ea795c 100755..100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -246,8 +246,8 @@ module ActiveRecord # QUOTING ================================================== def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) - s = column.class.string_to_binary(value).unpack("H*")[0] + if value.kind_of?(String) && column && column.type == :binary + s = value.unpack("H*")[0] "x'#{s}'" elsif value.kind_of?(BigDecimal) value.to_s("F") diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index cc02b313e1..bccfa41ad1 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -119,17 +119,7 @@ module ActiveRecord type_cast(default) end - # Used to convert from Strings to BLOBs - def string_to_binary(value) - self.class.string_to_binary(value) - end - class << self - # Used to convert from Strings to BLOBs - def string_to_binary(value) - value - end - # Used to convert from BLOBs to Strings def binary_to_string(value) value diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 92796c996e..92796c996e 100755..100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 15b5452850..15b5452850 100755..100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 56dc9ea813..ef7b976d4f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -100,7 +100,11 @@ module ActiveRecord if string.nil? nil elsif String === string - IPAddr.new(string) + begin + IPAddr.new(string) + rescue ArgumentError + nil + end else string end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index e1475416eb..df489a5b1f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -226,8 +226,8 @@ module ActiveRecord # QUOTING ================================================== def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) - s = column.class.string_to_binary(value).unpack("H*")[0] + if value.kind_of?(String) && column && column.type == :binary + s = value.unpack("H*")[0] "x'#{s}'" else super diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 9782a48055..01c73be849 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -73,7 +73,7 @@ module ActiveRecord [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column_default, :add_reference, :remove_reference, :transaction, - :drop_join_table, :drop_table, :execute_block, + :drop_join_table, :drop_table, :execute_block, :enable_extension, :change_column, :execute, :remove_columns, # irreversible methods need to be here too ].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 @@ -100,6 +100,7 @@ module ActiveRecord add_column: :remove_column, add_timestamps: :remove_timestamps, add_reference: :remove_reference, + enable_extension: :disable_extension }.each do |cmd, inv| [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| class_eval <<-EOV, __FILE__, __LINE__ + 1 diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index e53e8553ad..df28451bb7 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -465,19 +465,17 @@ module ActiveRecord association.build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } - unless association.loaded? || call_reject_if(association_name, attributes) + unless call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's # proxy_target array (either by finding it, or adding it if not found) - target_record = association.target.detect { |record| record == existing_record } - + # Take into account that the proxy_target may have changed due to callbacks + target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s } if target_record existing_record = target_record else - association.add_to_target(existing_record) + association.add_to_target(existing_record, :skip_callbacks) end - end - if !call_reject_if(association_name, attributes) assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end else diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 8a311039d5..daccab762f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -320,11 +320,14 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent schema.rb file" task :load_schema => 'db:test:purge' do begin + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) ActiveRecord::Schema.verbose = false db_namespace["schema:load"].invoke ensure - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 73d154e03e..f428f160cf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -394,7 +394,7 @@ module ActiveRecord # returns either nil or the inverse association name that it finds. def automatic_inverse_of if can_find_inverse_of_automatically?(self) - inverse_name = active_record.name.downcase.to_sym + inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym begin reflection = klass.reflect_on_association(inverse_name) @@ -413,7 +413,7 @@ module ActiveRecord end # Checks if the inverse reflection that is returned from the - # +set_automatic_inverse_of+ method is a valid reflection. We must + # +automatic_inverse_of+ method is a valid reflection. We must # make sure that the reflection's active_record name matches up # with the current reflection's klass name. # diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 489efac932..b8dd35c4c5 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -66,7 +66,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase def test_write_value data = "\u001F" record = ByteaDataType.create(payload: data) - refute record.new_record? + assert_not record.new_record? assert_equal(data, record.payload) end @@ -74,14 +74,14 @@ class PostgresqlByteaTest < ActiveRecord::TestCase data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) record = ByteaDataType.create(payload: data) - refute record.new_record? + assert_not record.new_record? assert_equal(data, record.payload) assert_equal(data, ByteaDataType.where(id: record.id).first.payload) end def test_write_nil record = ByteaDataType.create(payload: nil) - refute record.new_record? + assert_not record.new_record? assert_equal(nil, record.payload) assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index e946b8bb22..75b6f4f8ce 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -311,11 +311,11 @@ _SQL def test_update_tstzrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') - assert @first_range.tstz_range = new_tstzrange + @first_range.tstz_range = new_tstzrange assert @first_range.save assert @first_range.reload assert_equal new_tstzrange, @first_range.tstz_range - assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') + @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') assert @first_range.save assert @first_range.reload assert_nil @first_range.tstz_range @@ -335,11 +335,11 @@ _SQL skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? tz = ::ActiveRecord::Base.default_timezone new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - assert @first_range.ts_range = new_tsrange + @first_range.ts_range = new_tsrange assert @first_range.save assert @first_range.reload assert_equal new_tsrange, @first_range.ts_range - assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) + @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) assert @first_range.save assert @first_range.reload assert_nil @first_range.ts_range @@ -357,11 +357,11 @@ _SQL def test_update_numrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - assert @first_range.num_range = new_numrange + @first_range.num_range = new_numrange assert @first_range.save assert @first_range.reload assert_equal new_numrange, @first_range.num_range - assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') + @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') assert @first_range.save assert @first_range.reload assert_nil @first_range.num_range @@ -379,11 +379,11 @@ _SQL def test_update_daterange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) - assert @first_range.date_range = new_daterange + @first_range.date_range = new_daterange assert @first_range.save assert @first_range.reload assert_equal new_daterange, @first_range.date_range - assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) + @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) assert @first_range.save assert @first_range.reload assert_nil @first_range.date_range @@ -401,11 +401,11 @@ _SQL def test_update_int4range skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_int4range = 6...10 - assert @first_range.int4_range = new_int4range + @first_range.int4_range = new_int4range assert @first_range.save assert @first_range.reload assert_equal new_int4range, @first_range.int4_range - assert @first_range.int4_range = 3...3 + @first_range.int4_range = 3...3 assert @first_range.save assert @first_range.reload assert_nil @first_range.int4_range @@ -423,11 +423,11 @@ _SQL def test_update_int8range skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_int8range = 60000...10000000 - assert @first_range.int8_range = new_int8range + @first_range.int8_range = new_int8range assert @first_range.save assert @first_range.reload assert_equal new_int8range, @first_range.int8_range - assert @first_range.int8_range = 39999...39999 + @first_range.int8_range = 39999...39999 assert @first_range.save assert @first_range.reload assert_nil @first_range.int8_range @@ -435,10 +435,10 @@ _SQL def test_update_tsvector new_text_vector = "'new' 'text' 'vector'" - assert @first_tsvector.text_vector = new_text_vector + @first_tsvector.text_vector = new_text_vector assert @first_tsvector.save assert @first_tsvector.reload - assert @first_tsvector.text_vector = new_text_vector + @first_tsvector.text_vector = new_text_vector assert @first_tsvector.save assert @first_tsvector.reload assert_equal new_text_vector, @first_tsvector.text_vector @@ -479,11 +479,11 @@ _SQL def test_update_integer_array new_value = [32800,95000,29350,17000] - assert @first_array.commission_by_quarter = new_value + @first_array.commission_by_quarter = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.commission_by_quarter - assert @first_array.commission_by_quarter = new_value + @first_array.commission_by_quarter = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.commission_by_quarter @@ -491,11 +491,11 @@ _SQL def test_update_text_array new_value = ['robby','robert','rob','robbie'] - assert @first_array.nicknames = new_value + @first_array.nicknames = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.nicknames - assert @first_array.nicknames = new_value + @first_array.nicknames = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.nicknames @@ -503,7 +503,7 @@ _SQL def test_update_money new_value = BigDecimal.new('123.45') - assert @first_money.wealth = new_value + @first_money.wealth = new_value assert @first_money.save assert @first_money.reload assert_equal new_value, @first_money.wealth @@ -512,8 +512,8 @@ _SQL def test_update_number new_single = 789.012 new_double = 789012.345 - assert @first_number.single = new_single - assert @first_number.double = new_double + @first_number.single = new_single + @first_number.double = new_double assert @first_number.save assert @first_number.reload assert_equal new_single, @first_number.single @@ -521,7 +521,7 @@ _SQL end def test_update_time - assert @first_time.time_interval = '2 years 3 minutes' + @first_time.time_interval = '2 years 3 minutes' assert @first_time.save assert @first_time.reload assert_equal '2 years 00:03:00', @first_time.time_interval @@ -531,9 +531,9 @@ _SQL new_inet_address = '10.1.2.3/32' new_cidr_address = '10.0.0.0/8' new_mac_address = 'bc:de:f0:12:34:56' - assert @first_network_address.cidr_address = new_cidr_address - assert @first_network_address.inet_address = new_inet_address - assert @first_network_address.mac_address = new_mac_address + @first_network_address.cidr_address = new_cidr_address + @first_network_address.inet_address = new_inet_address + @first_network_address.mac_address = new_mac_address assert @first_network_address.save assert @first_network_address.reload assert_equal @first_network_address.cidr_address, new_cidr_address @@ -544,8 +544,8 @@ _SQL def test_update_bit_string new_bit_string = '11111111' new_bit_string_varying = '0xFF' - assert @first_bit_string.bit_string = new_bit_string - assert @first_bit_string.bit_string_varying = new_bit_string_varying + @first_bit_string.bit_string = new_bit_string + @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save assert @first_bit_string.reload assert_equal new_bit_string, @first_bit_string.bit_string @@ -558,9 +558,23 @@ _SQL assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save } end + def test_invalid_network_address + @first_network_address.cidr_address = 'invalid addr' + assert_nil @first_network_address.cidr_address + assert_equal 'invalid addr', @first_network_address.cidr_address_before_type_cast + assert @first_network_address.save + + @first_network_address.reload + + @first_network_address.inet_address = 'invalid addr' + assert_nil @first_network_address.inet_address + assert_equal 'invalid addr', @first_network_address.inet_address_before_type_cast + assert @first_network_address.save + end + def test_update_oid new_value = 567890 - assert @first_oid.obj_id = new_value + @first_oid.obj_id = new_value assert @first_oid.save assert @first_oid.reload assert_equal new_value, @first_oid.obj_id diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 724d1cbbf8..941e851aae 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -654,7 +654,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1) john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1) assert_equal sarah.agents, [john] - assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents + assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 71cf1237e8..2477e60e51 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -9,10 +9,24 @@ require 'models/rating' require 'models/comment' require 'models/car' require 'models/bulb' +require 'models/mixed_case_monkey' class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars + def test_has_one_and_belongs_to_should_find_inverse_automatically_on_multiple_word_name + monkey_reflection = MixedCaseMonkey.reflect_on_association(:man) + man_reflection = Man.reflect_on_association(:mixed_case_monkey) + + assert_respond_to monkey_reflection, :has_inverse? + assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse" + assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection" + + assert_respond_to man_reflection, :has_inverse? + assert man_reflection.has_inverse?, "The man reflection should have an inverse" + assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection" + end + def test_has_one_and_belongs_to_should_find_inverse_automatically car_reflection = Car.reflect_on_association(:bulb) bulb_reflection = Bulb.reflect_on_association(:car) @@ -406,7 +420,7 @@ class InverseHasManyTests < ActiveRecord::TestCase interest = Interest.create!(man: man) man.interests.find(interest.id) - refute man.interests.loaded? + assert_not man.interests.loaded? end def test_raise_record_not_found_error_when_invalid_ids_are_passed diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index c91cf89f6d..c91cf89f6d 100755..100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 2cad8a6d96..1b205d372f 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -242,6 +242,16 @@ module ActiveRecord add = @recorder.inverse_of :remove_belongs_to, [:table, :user] assert_equal [:add_reference, [:table, :user], nil], add end + + def test_invert_enable_extension + disable = @recorder.inverse_of :enable_extension, ['uuid-ossp'] + assert_equal [:disable_extension, ['uuid-ossp'], nil], disable + end + + def test_invert_disable_extension + enable = @recorder.inverse_of :disable_extension, ['uuid-ossp'] + assert_equal [:enable_extension, ['uuid-ossp'], nil], enable + end end end end diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb new file mode 100644 index 0000000000..43a69928b6 --- /dev/null +++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb @@ -0,0 +1,144 @@ +require "cases/helper" +require "models/pirate" +require "models/bird" + +class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase + Pirate.has_many(:birds_with_add_load, + :class_name => "Bird", + :before_add => proc { |p,b| + @@add_callback_called << b + p.birds_with_add_load.to_a + }) + Pirate.has_many(:birds_with_add, + :class_name => "Bird", + :before_add => proc { |p,b| @@add_callback_called << b }) + + Pirate.accepts_nested_attributes_for(:birds_with_add_load, + :birds_with_add, + :allow_destroy => true) + + def setup + @@add_callback_called = [] + @pirate = Pirate.new.tap do |pirate| + pirate.catchphrase = "Don't call me!" + pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] + pirate.save! + end + @birds = @pirate.birds.to_a + end + + def bird_to_update + @birds[0] + end + + def bird_to_destroy + @birds[1] + end + + def existing_birds_attributes + @birds.map do |bird| + bird.attributes.slice("id","name") + end + end + + def new_birds + @pirate.birds_with_add.to_a - @birds + end + + def new_bird_attributes + [{'name' => "New Bird"}] + end + + def destroy_bird_attributes + [{'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + end + + def update_new_and_destroy_bird_attributes + [{'id' => @birds[0].id.to_s, 'name' => 'New Name'}, + {'name' => "New Bird"}, + {'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + end + + # Characterizing when :before_add callback is called + test ":before_add called for new bird when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = new_bird_attributes + assert_new_bird_with_callback_called + end + + test ":before_add called for new bird when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = new_bird_attributes + assert_new_bird_with_callback_called + end + + def assert_new_bird_with_callback_called + assert_equal(1, new_birds.size) + assert_equal(new_birds, @@add_callback_called) + end + + test ":before_add not called for identical assignment when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = existing_birds_attributes + assert_callbacks_not_called + end + + test ":before_add not called for identical assignment when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = existing_birds_attributes + assert_callbacks_not_called + end + + test ":before_add not called for destroy assignment when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = destroy_bird_attributes + assert_callbacks_not_called + end + + test ":before_add not called for deletion assignment when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = destroy_bird_attributes + assert_callbacks_not_called + end + + def assert_callbacks_not_called + assert_empty new_birds + assert_empty @@add_callback_called + end + + # Ensuring that the records in the association target are updated, + # whether the association is loaded before or not + test "Assignment updates records in target when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add) + end + + test "Assignment updates records in target when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add) + end + + test("Assignment updates records in target when not loaded" + + " and callback loads target") do + assert_not @pirate.birds_with_add_load.loaded? + @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add_load) + end + + test("Assignment updates records in target when loaded" + + " and callback loads target") do + @pirate.birds_with_add_load.load_target + @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add_load) + end + + def assert_assignment_affects_records_in_target(association_name) + association = @pirate.send(association_name) + assert association.detect {|b| b == bird_to_update }.name_changed?, + 'Update record not updated' + assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?, + 'Destroy record not marked for destruction' + end +end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 85fbe01416..44b2064110 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -184,25 +184,6 @@ module ActiveRecord assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:binary)) end - def test_quote_binary_with_string_to_binary - col = Class.new(FakeColumn) { - def string_to_binary(value) - 'foo' - end - }.new(:binary) - assert_equal "'foo'", @quoter.quote('lo\l', col) - end - - def test_quote_as_mb_chars_binary_column_with_string_to_binary - col = Class.new(FakeColumn) { - def string_to_binary(value) - 'foo' - end - }.new(:binary) - string = ActiveSupport::Multibyte::Chars.new('lo\l') - assert_equal "'foo'", @quoter.quote(string, col) - end - def test_string_with_crazy_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo)) end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bf9d395b2d..e1a760d240 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -170,6 +170,10 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:fourth).title, topics.first.title end + def test_order_with_hash_and_symbol_generates_the_same_sql + assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql + end + def test_raising_exception_on_invalid_hash_params assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) } end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index abfc90474c..a5cb22aaf6 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -453,11 +453,6 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end end - - ensure - Topic.reset_column_information # reset the column information to get correct reading - Topic.connection.remove_column('topics', 'stuff') if Topic.column_names.include?('stuff') - Topic.reset_column_information # reset the column information again for other tests end def test_transactions_state_from_rollback diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 7dad8041f3..feb828de31 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -8,7 +8,9 @@ class Author < ActiveRecord::Base has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post" has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post" + has_many :posts_containing_the_letter_a, :class_name => "Post" has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization' + has_many :posts_with_extension, :class_name => "Post" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post' has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post' has_many :comments, through: :posts do @@ -30,6 +32,7 @@ class Author < ActiveRecord::Base has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post' has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments + has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments has_many :funky_comments, :through => :posts, :source => :comments has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb index 82c6544bd5..d720e2be5e 100644 --- a/activerecord/test/models/auto_id.rb +++ b/activerecord/test/models/auto_id.rb @@ -1,4 +1,4 @@ class AutoId < ActiveRecord::Base - self.table_name = "auto_id_tests" - self.primary_key = "auto_id" + def self.table_name () "auto_id_tests" end + def self.primary_key () "auto_id" end end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 6d257dbe7e..a14a9febba 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,9 +1,12 @@ class Car < ActiveRecord::Base + has_many :bulbs has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" + has_many :frickinawesome_bulbs, -> { where :frickinawesome => true }, :class_name => "Bulb" has_one :bulb + has_one :frickinawesome_bulb, -> { where :frickinawesome => true }, :class_name => "Bulb" has_many :tyres has_many :engines, :dependent => :destroy diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb index 3d87eb795c..545aa8110d 100644 --- a/activerecord/test/models/citation.rb +++ b/activerecord/test/models/citation.rb @@ -1,3 +1,6 @@ class Citation < ActiveRecord::Base belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id + + belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id + belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id end diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 566e0873f1..816c5e6937 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -2,6 +2,7 @@ class Club < ActiveRecord::Base has_one :membership has_many :memberships, :inverse_of => false has_many :members, :through => :memberships + has_many :current_memberships has_one :sponsor has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" belongs_to :category diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 8104c607b5..c5d4ec0833 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -49,6 +49,7 @@ class Firm < Company has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client" has_many :plain_clients, :class_name => 'Client' + has_many :readonly_clients, -> { readonly }, :class_name => 'Client' has_many :clients_using_primary_key, :class_name => 'Client', :primary_key => 'name', :foreign_key => 'firm_name' has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client', @@ -166,6 +167,7 @@ class ExclusivelyDependentFirm < Company has_one :account, :foreign_key => "firm_id", :dependent => :delete has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all + has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(:name => 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all end class SpecialClient < Client diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb index 4bff92dc98..f4d127730c 100644 --- a/activerecord/test/models/man.rb +++ b/activerecord/test/models/man.rb @@ -6,4 +6,5 @@ class Man < ActiveRecord::Base # These are "broken" inverse_of associations for the purposes of testing has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man has_many :secret_interests, :class_name => 'Interest', :inverse_of => :secret_man + has_one :mixed_case_monkey end diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 72095f9236..cc47c7bc18 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -2,6 +2,7 @@ class Member < ActiveRecord::Base has_one :current_membership has_one :selected_membership has_one :membership + has_many :fellow_members, :through => :club, :source => :members has_one :club, :through => :current_membership has_one :selected_club, :through => :selected_membership, :source => :club has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb index 763baefd91..4d37371777 100644 --- a/activerecord/test/models/mixed_case_monkey.rb +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -1,3 +1,5 @@ class MixedCaseMonkey < ActiveRecord::Base self.primary_key = 'monkeyID' + + belongs_to :man end diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb index c441be2bef..6384b4c801 100644 --- a/activerecord/test/models/movie.rb +++ b/activerecord/test/models/movie.rb @@ -1,3 +1,5 @@ class Movie < ActiveRecord::Base - self.primary_key = "movieid" + def self.primary_key + "movieid" + end end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 77564ffad4..93a7a2073c 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -122,6 +122,7 @@ class Post < ActiveRecord::Base has_many :secure_readers has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader" has_many :people, :through => :readers + has_many :secure_people, :through => :secure_readers has_many :single_people, :through => :readers has_many :people_with_callbacks, :source=>:person, :through => :readers, :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 7f42a4b1f8..c094b726b4 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -1,6 +1,7 @@ class Project < ActiveRecord::Base has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' } has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer" + has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer" has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer' has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer" has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 40c8e97fc2..17035bf338 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -34,6 +34,7 @@ class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count' + has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" |