diff options
Diffstat (limited to 'activerecord')
18 files changed, 209 insertions, 110 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 6494c7374e..f282029d22 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,26 @@ +* Add a `:foreign_key` option to `references` and associated migration + methods. The model and migration generators now use this option, rather than + the `add_foreign_key` form. + + *Sean Griffin* + +* Don't raise when writing an attribute with an out-of-range datetime passed + by the user. + + *Grey Baker* + +* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with + `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`. + + *Yves Senn* + +* Fixes bug with 'ActiveRecord::Type::Numeric' that causes negative values to + be marked as having changed when set to the same negative value. + + Closes #18161. + + *Daniel Fox* + * Introduce `force: :cascade` option for `create_table`. Using this option will recreate tables even if they have dependent objects (like foreign keys). `db/schema.rb` now uses `force: :cascade`. This makes it possible to @@ -13,7 +36,7 @@ *Yves Senn* -* Fix undesirable RangeError by Type::Integer. Add Type::UnsignedInteger. +* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`. *Ryuta Kamizono* 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 3f4d3bfc08..7a050ca224 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -13,21 +13,6 @@ 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 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 - super - end - end - def concat(*records) unless owner.new_record? records.flatten.each do |record| diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 87274dd4e1..777f7ab4d7 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -12,7 +12,11 @@ module ActiveRecord if value.is_a?(Array) value.map { |v| type_cast_from_user(v) } elsif value.respond_to?(:in_time_zone) - value.in_time_zone || super + begin + value.in_time_zone || super + rescue ArgumentError + nil + end end end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 3288108a6a..08f274fd42 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -53,9 +53,9 @@ module ActiveRecord # store_listing.price_in_cents # => 10 # # Users may also define their own custom types, as long as they respond to the methods - # defined on the value type. The `type_cast` method on your type object will be called + # defined on the value type. The +type_cast+ method on your type object will be called # with values both from the database, and from your controllers. See - # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your + # +ActiveRecord::Attributes::Type::Value+ for the expected API. It is recommended that your # type objects inherit from an existing type, or the base value type. # # class MoneyType < ActiveRecord::Type::Integer diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 0fa00d03a3..3968b90341 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -363,7 +363,7 @@ module ActiveRecord conn.expire end - release owner + release conn, owner @available.add conn end @@ -376,7 +376,7 @@ module ActiveRecord @connections.delete conn @available.delete conn - release conn.owner + release conn, conn.owner @available.add checkout_new_connection if @available.any_waiting? end @@ -424,10 +424,12 @@ module ActiveRecord end end - def release(owner) + def release(conn, owner) thread_id = owner.object_id - @reserved_connections.delete thread_id + if @reserved_connections[thread_id] == conn + @reserved_connections.delete thread_id + end end def new_connection 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 11c2e8f773..8defc3986f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -68,6 +68,84 @@ module ActiveRecord end end + class ReferenceDefinition # :nodoc: + def initialize( + name, + polymorphic: false, + index: false, + foreign_key: false, + type: :integer, + **options + ) + @name = name + @polymorphic = polymorphic + @index = index + @foreign_key = foreign_key + @type = type + @options = options + + if polymorphic && foreign_key + raise ArgumentError, "Cannot add a foreign key to a polymorphic relation" + end + end + + def add_to(table) + columns.each do |column_options| + table.column(*column_options) + end + + if index + table.index(column_names, index_options) + end + + if foreign_key + table.foreign_key(foreign_table_name, foreign_key_options) + end + end + + protected + + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + + private + + def as_options(value, default = {}) + if value.is_a?(Hash) + value + else + default + end + end + + def polymorphic_options + as_options(polymorphic, options) + end + + def index_options + as_options(index) + end + + def foreign_key_options + as_options(foreign_key) + end + + def columns + result = [["#{name}_id", type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result + end + + def column_names + columns.map(&:first) + end + + def foreign_table_name + name.to_s.pluralize + end + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # @@ -314,36 +392,9 @@ module ActiveRecord # t.belongs_to(:supplier, polymorphic: true) # # See SchemaStatements#add_reference - def references( - *args, - polymorphic: false, - index: false, - foreign_key: false, - type: :integer, - **options - ) - polymorphic_options = polymorphic.is_a?(Hash) ? polymorphic : options - index_options = index.is_a?(Hash) ? index : {} - foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {} - - if polymorphic && foreign_key - raise ArgumentError, "Cannot add a foreign key on a polymorphic relation" - end - + def references(*args, **options) args.each do |col| - column("#{col}_id", type, options) - - if polymorphic - column("#{col}_type", :string, polymorphic_options) - end - - if index - self.index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options) - end - - if foreign_key - self.foreign_key(col.to_s.pluralize, foreign_key_options) - end + ReferenceDefinition.new(col, **options).add_to(self) end end alias :belongs_to :references @@ -597,6 +648,10 @@ module ActiveRecord end end + def foreign_key(*args) # :nodoc: + @base.add_foreign_key(name, *args) + end + private def native @base.native_database_types 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 a19bcc86c2..6e42089801 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -645,36 +645,8 @@ module ActiveRecord # # add_reference(:products, :supplier, foreign_key: true) # - def add_reference( - table_name, - ref_name, - polymorphic: false, - index: false, - foreign_key: false, - type: :integer, - **options - ) - polymorphic_options = polymorphic.is_a?(Hash) ? polymorphic : options - index_options = index.is_a?(Hash) ? index : {} - foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {} - - if polymorphic && foreign_key - raise ArgumentError, "Cannot add a foreign key to a polymorphic relation" - end - - add_column(table_name, "#{ref_name}_id", type, options) - - if polymorphic - add_column(table_name, "#{ref_name}_type", :string, polymorphic_options) - end - - if index - add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options) - end - - if foreign_key - add_foreign_key(table_name, ref_name.to_s.pluralize, foreign_key_options) - end + def add_reference(table_name, *args) + ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self)) end alias :add_belongs_to :add_reference diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 8a2a06f2ca..0f9b52f69f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -81,6 +81,9 @@ module ActiveRecord # # Note that the model will _not_ be destroyed until the parent is saved. # + # Also note that the model will not be destroyed unless you also specify + # its id in the updated hash. + # # === One-to-many # # Consider a member that has a number of posts: diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 4daf2a0e2b..04c2be045d 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -319,7 +319,7 @@ db_namespace = namespace :db do begin should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Schema.verbose = false - ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA'] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA'] ensure if should_reconnect ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) @@ -329,7 +329,7 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent structure.sql file" task :load_structure => %w(db:test:purge) do - ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] end # desc "Recreate the test database from a fresh schema" diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 6ed610e5f0..69aceb66b1 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -188,27 +188,7 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename) end - def load_schema(format = ActiveRecord::Base.schema_format, file = nil) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - This method will act on a specific connection in the future. - To act on the current connection, use `load_schema_current` instead. - MSG - - load_schema_current(format, file) - end - - def schema_file(format = ActiveSupport::Base.schema_format) - case format - when :ruby - File.join(db_dir, "schema.rb") - when :sql - File.join(db_dir, "structure.sql") - end - end - - # This method is the successor of +load_schema+. We should rename it - # after +load_schema+ went through a deprecation cycle. (Rails > 4.2) - def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: file ||= schema_file(format) case format @@ -224,6 +204,23 @@ module ActiveRecord end end + def load_schema_for(*args) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + This method was renamed to `#load_schema` and will be removed in the future. + Use `#load_schema` instead. + MSG + load_schema(*args) + end + + def schema_file(format = ActiveSupport::Base.schema_format) + case format + when :ruby + File.join(db_dir, "schema.rb") + when :sql + File.join(db_dir, "structure.sql") + end + end + def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env) if File.exist?(file || schema_file(format)) load_schema_current(format, file, environment) @@ -232,7 +229,7 @@ module ActiveRecord def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) each_current_configuration(environment) { |configuration| - load_schema_for configuration, format, file + load_schema configuration, format, file } ActiveRecord::Base.establish_connection(environment.to_sym) end diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb index fa43266504..674f996f38 100644 --- a/activerecord/lib/active_record/type/numeric.rb +++ b/activerecord/lib/active_record/type/numeric.rb @@ -29,7 +29,7 @@ module ActiveRecord # 'wibble'.to_i will give zero, we want to make sure # that we aren't marking int zero to string zero as # changed. - value.to_s !~ /\A\d+\.?\d*\z/ + value.to_s !~ /\A-?\d+\.?\d*\z/ end end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 3191a868ef..3cac03464e 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -29,7 +29,7 @@ module ActiveRecord def changed_in_place?(raw_old_value, value) return false if value.nil? - subtype.changed_in_place?(raw_old_value, coder.dump(value)) + subtype.changed_in_place?(raw_old_value, type_cast_for_database(value)) end def accessor diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 9456a4a56c..75679b8692 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -91,8 +91,8 @@ module ActiveRecord # Convenience method for types which do not need separate type casting # behavior for user and database inputs. Called by - # `type_cast_from_database` and `type_cast_from_user` for all values - # except `nil`. + # +type_cast_from_database+ and +type_cast_from_user+ for all values + # except +nil+. def cast_value(value) # :doc: value end diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index c0491bbee5..c2ec92c40d 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -3,6 +3,8 @@ require 'models/topic' require 'models/task' class DateTimeTest < ActiveRecord::TestCase + include InTimeZone + def test_saves_both_date_and_time with_env_tz 'America/New_York' do with_timezone_config default: :utc do @@ -29,6 +31,14 @@ class DateTimeTest < ActiveRecord::TestCase assert_nil task.ending end + def test_assign_bad_date_time_with_timezone + in_time_zone "Pacific Time (US & Canada)" do + task = Task.new + task.starting = '2014-07-01T24:59:59GMT' + assert_nil task.starting + end + end + def test_assign_empty_date topic = Topic.new topic.last_read = '' diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 98888150a8..287a3f33ea 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -35,6 +35,22 @@ class PooledConnectionsTest < ActiveRecord::TestCase end end + def checkout_checkin_connections_loop(pool_size, loops) + ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) + @connection_count = 0 + @timed_out = 0 + loops.times do + begin + conn = ActiveRecord::Base.connection_pool.checkout + ActiveRecord::Base.connection_pool.checkin conn + @connection_count += 1 + ActiveRecord::Base.connection.tables + rescue ActiveRecord::ConnectionTimeoutError + @timed_out += 1 + end + end + end + def test_pooled_connection_checkin_one checkout_checkin_connections 1, 2 assert_equal 2, @connection_count @@ -42,6 +58,20 @@ class PooledConnectionsTest < ActiveRecord::TestCase assert_equal 1, ActiveRecord::Base.connection_pool.connections.size end + def test_pooled_connection_checkin_two + checkout_checkin_connections_loop 2, 3 + assert_equal 3, @connection_count + assert_equal 0, @timed_out + assert_equal 2, ActiveRecord::Base.connection_pool.connections.size + end + + def test_pooled_connection_remove + ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5})) + old_connection = ActiveRecord::Base.connection + extra_connection = ActiveRecord::Base.connection_pool.checkout + ActiveRecord::Base.connection_pool.remove(extra_connection) + assert_equal ActiveRecord::Base.connection, old_connection + end private diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 56a0e92e1d..c8441201ca 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -256,4 +256,12 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_equal("second", t.content) assert_equal("second", t.reload.content) end + + def test_nil_is_not_changed_when_serialized_with_a_class + Topic.serialize(:content, Array) + + topic = Topic.new(content: nil) + + assert_not topic.content_changed? + end end diff --git a/activerecord/test/cases/type/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb index c028aa52af..34ed1d7b19 100644 --- a/activerecord/test/cases/type/decimal_test.rb +++ b/activerecord/test/cases/type/decimal_test.rb @@ -38,6 +38,14 @@ module ActiveRecord type = Decimal.new assert_equal BigDecimal("1"), type.type_cast_from_user(value) end + + def test_changed? + type = Decimal.new + + assert type.changed?(5.0, 5.0, '5.0wibble') + assert_not type.changed?(5.0, 5.0, '5.0') + assert_not type.changed?(-5.0, -5.0, '-5.0') + end end end end diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index 5942f77e18..af4d0b4642 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -47,6 +47,8 @@ module ActiveRecord assert type.changed?(5, 5, '5wibble') assert_not type.changed?(5, 5, '5') assert_not type.changed?(5, 5, '5.0') + assert_not type.changed?(-5, -5, '-5') + assert_not type.changed?(-5, -5, '-5.0') assert_not type.changed?(nil, nil, nil) end |
