You can just validate it in the controller, count the phones and render a flash error, before you actually try and save the records.
Doing this in callbacks is difficult and not foolproof.
我添加了一些快速测试,以涵盖创建手机的不同方式:
# spec/models/homes_phone_spec.rb
require "rails_helper"
RSpec.describe HomesPhone, type: :model do
it "saves 3 new phones" do
home = Home.create(phones: 3.times.map { Phone.new })
expect(home.phones.count).to eq 3
end
it "doesn't save 4 new phones" do
home = Home.create(phones: 4.times.map { Phone.new })
expect(home.phones.count).to eq 0
expect(home.phones.size).to eq 4
end
it "can create up to 3 three phones through association" do
home = Home.create!
expect do
5.times { home.phones.create }
end.to change(home.phones, :count).by(3)
end
it "doesn't add 4th phone to existing record" do
Home.create(phones: 3.times.map { Phone.new })
home = Home.last
# NOTE: every time you call `valid?` or `validate`, it runs validations
# again and all previous errors are cleared.
# expect(home.phones.create).to be_invalid
expect(home.phones.create).to be_new_record
# or
# expect(home.phones.create).not_to be_persisted
# expect(home.phones.create.errors.any?).to be true
end
it "adds phone limit validation error to Phone" do
home = Home.create(phones: 3.times.map { Phone.new })
phone = home.phones.create
expect(phone.errors[:base]).to eq ["too many phones"]
end
end
# app/models/phone.rb
class Phone < ApplicationRecord
has_many :homes_phones, dependent: :destroy
has_many :homes, through: :homes_phones
end
# app/models/home.rb
class Home < ApplicationRecord
has_many :homes_phones, dependent: :destroy
has_many :phones, through: :homes_phones
end
# app/models/homes_phone.rb
class HomesPhone < ApplicationRecord
belongs_to :home
# NOTE: setting `inverse_of` option will stop raising errors and
# just return `phone` with errors that we'll add below
belongs_to :phone, inverse_of: :homes_phones
# because of the way through association is saved with `save!` call
# it raises validation errors. `inverse_of` allows the through association
# to be properly built and it skips `save!` call:
# https://github.com/rails/rails/blob/v7.0.4.2/activerecord/lib/active_record/associations/has_many_through_association.rb#L79-L80
validate do
# NOTE: if you use `homes_phones` association, the `size` method returns
# current count, instead of a lagging `phones.size` count.
if home.homes_phones.size > 3
errors.add(:base, "too many phones")
phone.errors.merge!(errors)
end
end
end
$ rspec spec/models/homes_phone_spec.rb
HomesPhone
saves 3 new phones
doesn't save 4 new phones
can create up to 3 three phones through association
doesn't add 4th phone to existing record
adds phone limit validation to Phone
Finished in 0.10967 seconds (files took 2.35 seconds to load)
5 examples, 0 failures
但它并不涵盖所有内容,比如:
it "can append up to 3 three phones" do
home = Home.create!
expect do
5.times { home.phones << Phone.new }
end.to change(home.phones, :count).by(3)
end
$ rspec spec/models/homes_phone_spec.rb:38
Run options: include {:locations=>{"./spec/models/homes_phone_spec.rb"=>[38]}}
HomesPhone
can append up to 3 three phones (FAILED - 1)
我以为我把一切都修好了.您可以try 执行以下操作:
after_validation do
if home.homes_phones.size > 3
errors.add(:base, "too many phones")
phone.errors.merge! errors
raise ActiveRecord::Rollback
end
end
$ rspec spec/models/homes_phone_spec.rb -f p
......
Finished in 0.12411 seconds (files took 2.25 seconds to load)
6 examples, 0 failures