diff options
Diffstat (limited to 'activerecord')
26 files changed, 364 insertions, 52 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index bd86dfb88e..d880470d97 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,26 @@ +* Only try to nullify has_one target association if the record is persisted. + + Fixes #21223. + + *Agis Anastasopoulos* + +* Uniqueness validator raises descriptive error when running on a persisted + record without primary key. + + Closes #21304. + + *Yves Senn* + +* Add a native JSON data type support in MySQL. + + Example: + + create_table :json_data_type do |t| + t.json :settings + end + + *Ryuta Kamizono* + * Descriptive error message when fixtures contain a missing column. Closes #21201. diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index b5a8c81fe4..29c711082a 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -227,6 +227,31 @@ module ActiveRecord @association.last(*args) end + # Gives a record (or N records if a parameter is supplied) from the collection + # using the same rules as <tt>ActiveRecord::Base.take</tt>. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1> + # + # person.pets.take(2) + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1> + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.take # => nil + # another_person_without.pets.take(2) # => [] def take(n = nil) @association.take(n) end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 5a92bc5e8a..1829453d73 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -65,7 +65,7 @@ module ActiveRecord when :destroy target.destroy when :nullify - target.update_columns(reflection.foreign_key => nil) + target.update_columns(reflection.foreign_key => nil) if target.persisted? end end end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 6ecc741195..3992a240b9 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -116,7 +116,7 @@ module ActiveRecord when String preloaders_for_one(association.to_sym, records, scope) else - raise ArgumentError, "#{association.inspect} was not recognised for preload" + raise ArgumentError, "#{association.inspect} was not recognized for preload" end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index dbb0e2fab2..d0de42d27c 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -234,7 +234,7 @@ module ActiveRecord super end - # Marks this record to be destroyed as part of the parents save transaction. + # Marks this record to be destroyed as part of the parent's save transaction. # This does _not_ actually destroy the record instantly, rather child record will be destroyed # when <tt>parent.save</tt> is called. # @@ -243,7 +243,7 @@ module ActiveRecord @marked_for_destruction = true end - # Returns whether or not this record will be destroyed as part of the parents save transaction. + # Returns whether or not this record will be destroyed as part of the parent's save transaction. # # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model. def marked_for_destruction? 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 d17e272ed1..3115e03ea2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -214,6 +214,7 @@ module ActiveRecord @name = name end + # Returns an array of ColumnDefinition objects for the columns of the table. def columns; @columns_hash.values; end # Returns a ColumnDefinition for the column with name +name+. @@ -369,6 +370,8 @@ module ActiveRecord self end + # remove the column +name+ from the table. + # remove_column(:account_id) def remove_column(name) @columns_hash.delete name.to_s end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 56227ddd80..ed14c781c6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -266,6 +266,11 @@ module ActiveRecord false end + # Does this adapter support json data type? + def supports_json? + false + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end 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 af156c9c78..b3e55a0b90 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -10,6 +10,10 @@ module ActiveRecord options[:auto_increment] = true if type == :bigint super end + + def json(*args, **options) + args.each { |name| column(name, :json, options) } + end end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition @@ -242,17 +246,19 @@ module ActiveRecord QUOTED_TRUE, QUOTED_FALSE = '1', '0' NATIVE_DATABASE_TYPES = { - :primary_key => "int(11) auto_increment PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "int", :limit => 4 }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "tinyint", :limit => 1 } + primary_key: "int(11) auto_increment PRIMARY KEY", + string: { name: "varchar", limit: 255 }, + text: { name: "text" }, + integer: { name: "int", limit: 4 }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "datetime" }, + time: { name: "time" }, + date: { name: "date" }, + binary: { name: "blob" }, + boolean: { name: "tinyint", limit: 1 }, + bigint: { name: "bigint" }, + json: { name: "json" }, } INDEX_TYPES = [:fulltext, :spatial] @@ -790,6 +796,7 @@ module ActiveRecord m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) + m.register_type %r(^json)i, MysqlJson.new register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 @@ -1043,6 +1050,14 @@ module ActiveRecord end end + class MysqlJson < Type::Internal::AbstractJson # :nodoc: + def changed_in_place?(raw_old_value, new_value) + # Normalization is required because MySQL JSON data format includes + # the space between the elements. + super(serialize(deserialize(raw_old_value)), new_value) + end + end + class MysqlString < Type::String # :nodoc: def serialize(value) case value @@ -1063,6 +1078,8 @@ module ActiveRecord end end + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql) + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index b7db57c9fe..ff43c7ec42 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -41,6 +41,10 @@ module ActiveRecord true end + def supports_json? + version >= '5.7.8' + end + # HELPER METHODS =========================================== def each_hash(result) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb index 8e1256baad..dbc879ffd4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -2,32 +2,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Json < Type::Value # :nodoc: - include Type::Helpers::Mutable - - def type - :json - end - - def deserialize(value) - if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) rescue nil - else - value - end - end - - def serialize(value) - if value.is_a?(::Array) || value.is_a?(::Hash) - ::ActiveSupport::JSON.encode(value) - else - value - end - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end + class Json < Type::Internal::AbstractJson end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2c43c46a3d..27291bd2ea 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -201,6 +201,10 @@ module ActiveRecord true end + def supports_json? + postgresql_version >= 90200 + end + def index_algorithms { concurrently: 'CONCURRENTLY' } end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index d589620f8a..718f04871d 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -218,11 +218,12 @@ module ActiveRecord class UnknownPrimaryKey < ActiveRecordError attr_reader :model - def initialize(model) - super("Unknown primary key for table #{model.table_name} in model #{model}.") + def initialize(model, description = nil) + message = "Unknown primary key for table #{model.table_name} in model #{model}." + message += "\n#{description}" if description + super(message) @model = model end - end # Raised when a relation cannot be mutated because it's already loaded. diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b7b508d853..04c6124eef 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -376,6 +376,7 @@ module ActiveRecord attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: + # Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending. def check_pending!(connection = Base.connection) raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection) end @@ -408,7 +409,10 @@ module ActiveRecord new.migrate direction end - # Disable DDL transactions for this migration. + # Disable the transaction wrapping this migration. + # You can still create your own transactions even after calling #disable_ddl_transaction! + # + # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration]. def disable_ddl_transaction! @disable_ddl_transaction = true end @@ -810,7 +814,7 @@ module ActiveRecord new(:up, migrations, target_version).migrate end - def down(migrations_paths, target_version = nil, &block) + def down(migrations_paths, target_version = nil) migrations = migrations(migrations_paths) migrations.select! { |m| yield m } if block_given? diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 2c0cda69d0..53f3b53bec 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -20,6 +20,8 @@ require 'active_record/type/adapter_specific_registry' require 'active_record/type/type_map' require 'active_record/type/hash_lookup_type_map' +require 'active_record/type/internal/abstract_json' + module ActiveRecord module Type @registry = AdapterSpecificRegistry.new diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb new file mode 100644 index 0000000000..963a8245d0 --- /dev/null +++ b/activerecord/lib/active_record/type/internal/abstract_json.rb @@ -0,0 +1,33 @@ +module ActiveRecord + module Type + module Internal # :nodoc: + class AbstractJson < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + if value.is_a?(::String) + ::ActiveSupport::JSON.decode(value) rescue nil + else + value + end + end + + def serialize(value) + if value.is_a?(::Array) || value.is_a?(::Hash) + ::ActiveSupport::JSON.encode(value) + else + value + end + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end + end +end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 32d17a1392..5706bbd903 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -18,7 +18,11 @@ module ActiveRecord relation = build_relation(finder_class, table, attribute, value) if record.persisted? && finder_class.primary_key.to_s != attribute.to_s - relation = relation.where.not(finder_class.primary_key => record.id) + if finder_class.primary_key + relation = relation.where.not(finder_class.primary_key => record.id) + else + raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") + end end relation = scope_relation(record, table, relation) relation = relation.merge(options[:conditions]) if options[:conditions] diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb new file mode 100644 index 0000000000..c8c933af5e --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -0,0 +1,172 @@ +require 'cases/helper' +require 'support/schema_dumping_helper' + +if ActiveRecord::Base.connection.supports_json? +class Mysql2JSONTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper + self.use_transactional_tests = false + + class JsonDataType < ActiveRecord::Base + self.table_name = 'json_data_type' + + store_accessor :settings, :resolution + end + + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.create_table('json_data_type') do |t| + t.json 'payload' + t.json 'settings' + end + end + end + + def teardown + @connection.drop_table :json_data_type, if_exists: true + JsonDataType.reset_column_information + end + + def test_column + column = JsonDataType.columns_hash["payload"] + assert_equal :json, column.type + assert_equal 'json', column.sql_type + + type = JsonDataType.type_for_attribute("payload") + assert_not type.binary? + end + + def test_change_table_supports_json + @connection.change_table('json_data_type') do |t| + t.json 'users' + end + JsonDataType.reset_column_information + column = JsonDataType.columns_hash['users'] + assert_equal :json, column.type + end + + def test_schema_dumping + output = dump_table_schema("json_data_type") + assert_match(/t\.json\s+"settings"/, output) + end + + def test_cast_value_on_write + x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} + assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) + assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload) + x.save + assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload) + end + + def test_type_cast_json + type = JsonDataType.type_for_attribute("payload") + + data = "{\"a_key\":\"a_value\"}" + hash = type.deserialize(data) + assert_equal({'a_key' => 'a_value'}, hash) + assert_equal({'a_key' => 'a_value'}, type.deserialize(data)) + + assert_equal({}, type.deserialize("{}")) + assert_equal({'key'=>nil}, type.deserialize('{"key": null}')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) + end + + def test_rewrite + @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" + x = JsonDataType.first + x.payload = { '"a\'' => 'b' } + assert x.save! + end + + def test_select + @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" + x = JsonDataType.first + assert_equal({'k' => 'v'}, x.payload) + end + + def test_select_multikey + @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| + x = JsonDataType.first + assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload) + end + + def test_null_json + @connection.execute %q|insert into json_data_type (payload) VALUES(null)| + x = JsonDataType.first + assert_equal(nil, x.payload) + end + + def test_select_array_json_value + @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| + x = JsonDataType.first + assert_equal(['v0', {'k1' => 'v1'}], x.payload) + end + + def test_rewrite_array_json_value + @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| + x = JsonDataType.first + x.payload = ['v1', {'k2' => 'v2'}, 'v3'] + assert x.save! + end + + def test_with_store_accessors + x = JsonDataType.new(resolution: "320×480") + assert_equal "320×480", x.resolution + + x.save! + x = JsonDataType.first + assert_equal "320×480", x.resolution + + x.resolution = "640×1136" + x.save! + + x = JsonDataType.first + assert_equal "640×1136", x.resolution + end + + def test_duplication_with_store_accessors + x = JsonDataType.new(resolution: "320×480") + assert_equal "320×480", x.resolution + + y = x.dup + assert_equal "320×480", y.resolution + end + + def test_yaml_round_trip_with_store_accessors + x = JsonDataType.new(resolution: "320×480") + assert_equal "320×480", x.resolution + + y = YAML.load(YAML.dump(x)) + assert_equal "320×480", y.resolution + end + + def test_changes_in_place + json = JsonDataType.new + assert_not json.changed? + + json.payload = { 'one' => 'two' } + assert json.changed? + assert json.payload_changed? + + json.save! + assert_not json.changed? + + json.payload['three'] = 'four' + assert json.payload_changed? + + json.save! + json.reload + + assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) + assert_not json.changed? + end + + def test_assigning_invalid_json + json = JsonDataType.new + + json.payload = 'foo' + + assert_nil json.payload + end +end +end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index ffbf60e390..8ed7b4e39f 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1325,6 +1325,14 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_match message, error.message end + test "preload with invalid argument" do + exception = assert_raises(ArgumentError) do + Author.preload(10).to_a + end + assert_equal('10 was not recognized for preload', exception.message) + end + + test "preloading readonly association" do # has-one firm = Firm.where(id: "1").preload(:readonly_account).first! diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 5a8afaf4d2..d46e7ad235 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -107,6 +107,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nil Account.find(old_account_id).firm_id end + def test_nullification_on_destroyed_association + developer = Developer.create!(name: "Someone") + ship = Ship.create!(name: "Planet Caravan", developer: developer) + ship.destroy + assert !ship.persisted? + assert !developer.persisted? + end + def test_natural_assignment_to_nil_after_destroy firm = companies(:rails_core) old_account_id = firm.account.id diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index b2f209fe97..11e42f28dc 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -132,13 +132,9 @@ class MigrationTest < ActiveRecord::TestCase Person.connection.drop_table :testings2, if_exists: true end - def connection - ActiveRecord::Base.connection - end - def test_migration_instance_has_connection migration = Class.new(ActiveRecord::Migration).new - assert_equal connection, migration.connection + assert_equal ActiveRecord::Base.connection, migration.connection end def test_method_missing_delegates_to_connection diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 42e7507631..cdf63957f4 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -17,6 +17,7 @@ require 'models/minivan' require 'models/owner' require 'models/person' require 'models/pet' +require 'models/ship' require 'models/toy' require 'rexml/document' diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 1c919f0b57..5f6eb41240 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -7,6 +7,7 @@ require 'models/computer' require 'models/project' require 'models/reader' require 'models/person' +require 'models/ship' class ReadOnlyTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index ceca2c8366..7502a55391 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -4,6 +4,7 @@ require 'models/reply' require 'models/warehouse_thing' require 'models/guid' require 'models/event' +require 'models/dashboard' class Wizard < ActiveRecord::Base self.abstract_class = true @@ -446,4 +447,26 @@ class UniquenessValidationTest < ActiveRecord::TestCase key2.key_number = 10 assert_not key2.valid? end + + def test_validate_uniqueness_without_primary_key + klass = Class.new(ActiveRecord::Base) do + self.table_name = "dashboards" + + validates_uniqueness_of :dashboard_id + + def self.name; "Dashboard" end + end + + abc = klass.create!(dashboard_id: "abc") + assert klass.new(dashboard_id: "xyz").valid? + assert_not klass.new(dashboard_id: "abc").valid? + + abc.dashboard_id = "def" + + e = assert_raises ActiveRecord::UnknownPrimaryKey do + abc.save! + end + assert_match(/\AUnknown primary key for table dashboards in model/, e.message) + assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message) + end end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index d2a5a7fc49..8ac7a9df6a 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -50,6 +50,7 @@ class Developer < ActiveRecord::Base has_many :firms, :through => :contracts, :source => :firm has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") } has_many :ratings, through: :comments + has_one :ship, dependent: :nullify scope :jamises, -> { where(:name => 'Jamis') } diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 95172e4d3e..e333b964ab 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -3,6 +3,7 @@ class Ship < ActiveRecord::Base belongs_to :pirate belongs_to :update_only_pirate, :class_name => 'Pirate' + belongs_to :developer, dependent: :destroy has_many :parts, :class_name => 'ShipPart' has_many :treasures diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 6f34115534..994ece9244 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -673,6 +673,7 @@ ActiveRecord::Schema.define do create_table :ships, force: true do |t| t.string :name t.integer :pirate_id + t.belongs_to :developer t.integer :update_only_pirate_id # Conventionally named column for counter_cache t.integer :treasures_count, default: 0 |