Note that in the associated model, the behavior when new => child record build => parent record save! Is complicated.
(Maybe it's just a lack of understanding.)
Verification environment: ActiveRecord 6.0.2.1
--In has_one, the validity of the parent record and the child record are independent. --In has_many, if even one child record is invalid, the parent record is also invalid.
When you save! The parent record
--Raise if the parent record is invalid --If both parent record and child record are valid, insert is executed surrounded by transaction --If the parent record is valid and the child record is invalid (it can only be has_one due to the above properties), only the insert process of the parent record is executed.
Both has_one and has_many have an option called validate, which is false for has_one and true for has_many, which is the behavior of this article. The behavior around here can be controlled by specifying the validate option.
The sample is below.
migrations:
class CreateTables < ActiveRecord::Migration[6.0]
def change
create_table :customers do |t|
end
create_table :banks do |t|
end
create_table :merchants do |t|
end
create_table :bank_accounts do |t|
t.references :customer, foreign_key: true, null: false
t.references :bank, foreign_key: true, null: false
t.string :account_number, null: false
end
create_table :orders do |t|
t.references :customer, foreign_key: true, null: false
t.references :merchant, foreign_key: true, null: false
t.integer :price, null: false
end
end
end
models:
class Customer < ApplicationRecord
has_one :bank_account
has_many :orders
end
class Bank < ApplicationRecord
end
class Merchant < ApplicationRecord
end
class BankAccount < ApplicationRecord
belongs_to :customer
validates :account_number, presence: true
end
class Order < ApplicationRecord
belongs_to :customer
validates :price, presence: true
end
The case to experiment is as follows.
1-1 invalid
irb(main):118:0> customer = Customer.new
irb(main):119:0> customer.build_bank_account
=> #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: nil, created_at: nil, updated_at: nil>
irb(main):120:0> customer.valid?
=> true
irb(main):121:0> customer.bank_account.valid?
=> false
irb(main):122:0> customer.bank_account.errors.full_messages
=> ["Account number can't be blank"]
irb(main):123:0> customer.save!
=> true
irb(main):125:0> customer.persisted?
=> true
irb(main):126:0> customer.bank_account.persisted?
=> false
1-2 valid & insertable
irb(main):156:0> customer = Customer.new
irb(main):157:0> customer.build_bank_account(account_number: "1234", bank_id: 1)
=> #<BankAccount id: nil, customer_id: nil, bank_id: 1, account_number: "1234", created_at: nil, updated_at: nil>
irb(main):158:0> customer.valid?
=> true
irb(main):159:0> customer.bank_account.valid?
=> true
irb(main):160:0> customer.save!
=> true
irb(main):161:0> customer.persisted?
=> true
irb(main):162:0> customer.bank_account.persisted?
=> true
1-3 valid & uninsertable
irb(main):138:0> customer = Customer.new
irb(main):139:0> customer.build_bank_account(account_number: "1234")
=> #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: "1234", created_at: nil, updated_at: nil>
irb(main):140:0> customer.valid?
=> true
irb(main):141:0> customer.bank_account.valid?
=> true
irb(main):142:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'bank_id' doesn't have a default value)
irb(main):143:0> customer.persisted?
=> false
irb(main):144:0> customer.bank_account.persisted?
=> false
2-1 all invalid
irb(main):176:0> customer = Customer.new
irb(main):177:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):178:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):187:0> customer.valid?
=> false
irb(main):188:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):191:0> customer.orders.map {|order| order.valid? }
=> [false, false]
irb(main):193:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], ["Price can't be blank"]]
irb(main):196:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
2-2 all valid & insertable
irb(main):001:0> customer = Customer.new
irb(main):002:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):004:0> customer.orders.build(price: 200, merchant_id: 2)
=> #<Order id: nil, customer_id: nil, merchant_id: 2, price: 200, created_at: nil, updated_at: nil>
irb(main):005:0> customer.valid?
=> true
irb(main):006:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):007:0> customer.save!
=> true
irb(main):008:0> customer.persisted?
=> true
irb(main):009:0> customer.orders.map {|order| order.persisted? }
=> [true, true]
2-3 all valid & uninsertable
irb(main):016:0> customer = Customer.new
irb(main):017:0> customer.orders.build(price: 100)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil>
irb(main):018:0> customer.orders.build(price: 200)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil>
irb(main):019:0> customer.valid?
=> true
irb(main):020:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):021:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value)
irb(main):024:0> customer.persisted?
=> false
irb(main):025:0> customer.orders.map {|order| order.persisted? }
=> [false, false]
2-4 valid&savable + valid & uninsertable
irb(main):035:0> customer = Customer.new
irb(main):036:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):037:0> customer.orders.build(price: 200)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil>
irb(main):038:0> customer.valid?
=> true
irb(main):039:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):040:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value)
irb(main):041:0> customer.persisted?
=> false
irb(main):042:0> customer.orders.map {|order| order.persisted? }
=> [false, false]
2-5 invalid + valid & uninsertable
irb(main):044:0> customer = Customer.new
irb(main):045:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):046:0> customer.orders.build(price: 100)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil>
irb(main):047:0> customer.valid?
=> false
irb(main):048:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):049:0> customer.orders.map {|order| order.valid? }
=> [false, true]
irb(main):050:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], []]
irb(main):051:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
2-6 invalid + valid & insertable
irb(main):059:0> customer = Customer.new
irb(main):060:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):061:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):062:0> customer.valid?
=> false
irb(main):063:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):064:0> customer.orders.map {|order| order.valid? }
=> [false, true]
irb(main):065:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], []]
irb(main):066:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
Recommended Posts