Skip to content

Instantly share code, notes, and snippets.

@coralieco
Last active October 22, 2018 13:32
Show Gist options
  • Save coralieco/dd58e7c4e48068fb082a3cf4a7007f68 to your computer and use it in GitHub Desktop.
Save coralieco/dd58e7c4e48068fb082a3cf4a7007f68 to your computer and use it in GitHub Desktop.
errors.details for belongs_to validation

errors.details is { error: blank } on belongs_to validation

With Rails 5, there is a validation on belongs_to association.

Actual behavior

If you have:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

and try to create a comment in console

comment = Comment.new
#=> <Comment id: nil, post_id: nil>

comment.save
=> false

comment.save!
=> ActiveRecord::RecordInvalid Exception: Validation failed: Post must exist

The issue I encountered is in the details of errors

comment.errors.messages
{:post=>["must exist"]}

comment.errors.details
{:post=>[{:error=>:blank}]}

Expected behavior

But the {:error=>:blank} error is exactly the same detail error than for the validates_presence_of. What I would expect for the belongs_to error is:

comment.errors.details
{:post=>[{:error=>:required}]}

Especially since the message related to this error is must exist

As it is suggested in locales, we do have required: must exist https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml#L130

Steps to reproduce

Here is a script to reproduce the behaviour with tests to explain my problem more precisely:

# frozen_string_literal: true

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # Activate the gem you are reporting the issue against.
  gem "activerecord", "5.2.0"
  gem 'activemodel'
  gem "sqlite3"
  gem 'byebug'
end

require "active_record"
require "minitest/autorun"
require "logger"
require 'active_model'


# Ensure backward compatibility with minitest 4.
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post, optional: false
  # I add this validation as well so it is more obvious how confusing it is to have errors on both `validates_presence_of` and `belongs_to`
  validates_presence_of :post
end

class BugTest < Minitest::Test
  def test_association_stuff
    comment = Comment.new
    comment.save

    # Payload for comment.errors.messages
    json_messages_response = { :post=>["must exist", "can't be blank"] }
    assert_equal(json_messages_response, comment.errors.messages)

    # Payload for comment.errors.details
    json_messages_details = { :post=>[{:error=>:blank}, {:error=>:blank}] }
    assert_equal(json_messages_details, comment.errors.details)

    # What I would expect for comment.errors.details to have a clear distinction on which validation fails
    json_messages_details = { :post=>[{:error=>:required}, {:error=>:blank}] }
    assert_equal(json_messages_details, comment.errors.details)
  end
end

I had to precise optional: false in the script, but it is the case by default in rails 5.

In the example above, both comment.errors.details are {:error=>:blank}. There is no way of distinguishing the validation of validates_presence_of and belongs_to. Especially if only have a belongs_to association, reading the details is not much of help:

comment.errors.details
# :post=>[{:error=>:blank}]

I don't understand why we don't get:

comment.errors.details
# => :post=>[{:error=>:required}]

If anyone understands this behavious and can explain. Many thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment