Rails: Non null constraint exception when creating model with a has many through association

Created on 20 Oct 2016  路  3Comments  路  Source: rails/rails

Steps to reproduce

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"
  gem "rails", github: "rails/rails"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"

# 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
  create_table :comments, force: true
  create_table :post_comments do |t|
    t.integer :post_id, null: false
    t.integer :comment_id, null: false
  end
end

class Post < ActiveRecord::Base
  has_many :comments, through: :post_comments
  has_many :post_comments
end

class Comment < ActiveRecord::Base
  has_many :posts, through: :post_comments
  has_many :post_comments
end

class PostComment < ActiveRecord::Base
  belongs_to :post
  belongs_to :comment
end

class BugTest < Minitest::Test
  def test_association_stuff
    assert_instance_of Post, Post.create(comments: [Comment.create])
  end
end

Expected behaviour

That the post_comments insert has the post_id and does not violate the non null constraint. There are a couple of related issues 17015 and 16494 but this fails without using accepts_nested_attributes_for which the other two issues seem related to, if this is a bug I'd be happy to investigate, help out, and/or have a go at submitting a PR.

Actual behaviour

Transaction begins, comment is inserted, transaction is committed. Transaction begins, post is inserted, post comment is inserted but without the post_id.

BugTest#test_association_stuff:
ActiveRecord::StatementInvalid: SQLite3::ConstraintException: NOT NULL constraint failed: post_comments.post_id: INSERT INTO "post_comments" ("comment_id") VALUES (?)
D, [2016-10-19T22:44:28.638640 #19520] DEBUG -- :    (0.0ms)  begin transaction
D, [2016-10-19T22:44:28.639217 #19520] DEBUG -- :   SQL (0.0ms)  INSERT INTO "comments" DEFAULT VALUES
D, [2016-10-19T22:44:28.639376 #19520] DEBUG -- :    (0.0ms)  commit transaction
D, [2016-10-19T22:44:28.662915 #19520] DEBUG -- :    (0.0ms)  begin transaction
D, [2016-10-19T22:44:28.663491 #19520] DEBUG -- :   SQL (0.0ms)  INSERT INTO "posts" DEFAULT VALUES
D, [2016-10-19T22:44:28.665346 #19520] DEBUG -- :   SQL (0.2ms)  INSERT INTO "post_comments" ("comment_id") VALUES (?)  [["comment_id", 1]]
D, [2016-10-19T22:44:28.665540 #19520] DEBUG -- :    (0.0ms)  rollback transaction

System configuration

Rails version: 5.0.0.1 & 4.2.7.1

Ruby version: 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

activerecord attached PR

Most helpful comment

I've spent some time looking into this and the problem is the order of the has_many methods since in this example the through associations are defined before the associations that they go through. This issue only exists when the methods are called in the wrong order and a null constraint exists on the join table. However I think it could be useful to check for this when the associations are defined.

An option would be adding a check to ensure that the through association has already been defined in the case of having null constraints on the table and printing a warning. Or alternatively we could raise an ArgumentError (with deprecation warning first) as is done in other cases eg. when defining associations that conflict with AR methods. I'd be happy to work on this, let me know if anyone has feedback.

All 3 comments

I've spent some time looking into this and the problem is the order of the has_many methods since in this example the through associations are defined before the associations that they go through. This issue only exists when the methods are called in the wrong order and a null constraint exists on the join table. However I think it could be useful to check for this when the associations are defined.

An option would be adding a check to ensure that the through association has already been defined in the case of having null constraints on the table and printing a warning. Or alternatively we could raise an ArgumentError (with deprecation warning first) as is done in other cases eg. when defining associations that conflict with AR methods. I'd be happy to work on this, let me know if anyone has feedback.

@cih I believe that's the route to go聽(deprecation warning, for now). It's obviously an "edge case" since it hasn't been tackled before, but it's a definite change in behavior from 3.x to 4+. Seems there are quite a few people hitting it based on the dupes. Perhaps a note in the documentation as well?

Was this page helpful?
0 / 5 - 0 ratings