I have been stuck with this problem for a while and have thoroughly confused myself as to how nested models and validations work together.
In the code below, my aim is to have the creation of the parent model (Image or Video) fail if the validation of the child model(Content) fails. Currently, the parent model is being saved while the child model is not, and the validation errors are going unheard. If there are no validation errors, then everything works as expected.
#Image.rb
has_one :content,
as: :contentable,
inverse_of: :contentable,
dependent: :destroy
#Video.rb
has_one :content,
as: :contentable,
inverse_of: :contentable,
dependent: :destroy
#Content.rb
belongs_to :contentable,
inverse_of: :content,
polymorphic: true
validate :all_good?
def all_good?
errors.add(:base, "Nope!")
return false
end
Any leads or insights are much appreciated!
Rails has a special validation called validates_associated that ensures that the associated record(s) is valid. If the associated record is invalid then the parent record will also be invalid and an error for the association will be added to it's list of errors.
In both your Image and Video classes add the following:
validates_associated :content
Now if the content
association is invalid the video or image will not be saved.
video = Video.new
video.content = Content.new
video.save #=> false
video.valid? #=> false
video.errors #=> [:content => "is invalid"]
Short Answer
Add to image and video model:
accepts_nested_attributes_for :content
The Proof
I was quite sure I knew the answer to this but wasn't sure if it worked with polymorphic associations (which I haven't used before) so I set up a small test.
Created the models the same way as you have yours setup but with a name attribute and with a validation that I can use to test for failure.
class Image < ActiveRecord::Base
has_one :content,
as: :contentable,
inverse_of: :contentable,
dependent: :destroy
validates_length_of :name, maximum: 10
end
class Content < ActiveRecord::Base
belongs_to :contentable,
inverse_of: :content,
polymorphic: true
validates_length_of :name, maximum: 10
end
Next setup the migrations as so:
class CreateImages < ActiveRecord::Migration
def change
create_table :images do |t|
t.string :name
t.timestamps null: false
end
end
end
class CreateContents < ActiveRecord::Migration
def change
create_table :contents do |t|
t.string :name
t.references :contentable, polymorphic: true, index: true
t.timestamps null: false
end
end
end
Next write an RSpec to test that the parent isn't saved if child can't be saved and that validation errors perculate up.
it 'should not save image if content is invalid' do
image = Image.new()
image.name = 'this is ok'
expect(image).to be_valid
content = Content.new()
content.name = 'a string that should fail validation'
image.content = content
expect(image).to_not be_valid
image.save
expect(image).to_not be_persisted
expect(content).to_not be_persisted
expect(image.errors.count).to eq(1)
expect(image.content.errors[:name][0]).to include('is too long')
end
Ran the test and sure enough it fails.
Next add the following line to image (and video)
accepts_nested_attributes_for :content
The tests now pass - i.e., if the child fails validation the parent will also fail validation and will not save.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With