Validation of Shouts

Hi,

I’m at the end of the third lesson and have a problem with the validation of new TextShouts.
The problem is that I’m able to create a Shout with the content type TextShout where the TextShout has no body.

Thus, I get the following error afterwards:
ArgumentError in Dashboards#show

Showing .../thoughtbot/intermediate-rails/app/views/shouts/_shout.html.erb where line #6 raised:

'nil' is not an ActiveModel-compatible object that returns a valid partial path.

The Shout object looks like this:

=> #<Shout id: 27, user_id: 2, created_at: "2013-10-04 10:36:04", updated_at: "2013-10-04 10:36:04", content_type: "TextShout", content_id: nil>

I can also reproduce this in the Rails console:

irb(main):015:0* textshout = TextShout.new
=> #<TextShout id: nil, body: nil>
irb(main):016:0> textshout.valid?
=> false
irb(main):017:0> shout = User.first.shouts.new(content: textshout)
  User Load (0.3ms)  SELECT "users".* FROM "users" LIMIT 1
=> #<Shout id: nil, user_id: 1, created_at: nil, updated_at: nil, content_type: "TextShout", content_id: nil>
irb(main):018:0> shout.valid?
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
=> true

Does anyone else have that problem? The repo is here if you want to have a look: intermediate-rails

Thanks!

The linked repo is private so can’t have a look. I guess issue is the content_id is nil and you would render a different partial based on that? I am just shooting blank since I don’t participate in that workshop.

Thanks for helping out!
The repo can only be accessed by participants of the workshop IIRC.

You’re right, the content_id is nil for that record. But shouldn’t the Shout not even validate/be created if the contained TextShout is invalid?

Do you have a validation rule for that field on the model?
From your console example I suppose you don’t. You need to add something like this to the ShoutBox model:

validates :content_id, presence: true

I spent some time yesterday figuring out a solution to this problem so I thought I might as well post it here.

The problem is that validations run before either record is saved, so there won’t ever be a content_id when a new shout is being validated. The answer I came up with was to create a custom validator to check content.valid?, like so:

class TextShout < ActiveRecord::Base
  validates :body, presence: true
end
class Shout < ActiveRecord::Base
  belongs_to :user, required: true
  belongs_to :content, polymorphic: true, required: true, dependent: :destroy
  validate :content_must_be_valid
  default_scope { order(created_at: :desc) }

  protected
  def content_must_be_valid
    if content && content.invalid?
      content_errors = content.errors.full_messages.join(", ")
      errors.add(:content, "not valid: #{ content_errors }")
    end
  end
end

Works for me. Would love to know if there’s a better way, though.