rails: ActiveRecord::StatementInvalid error for Rails 6.1 HABTM relationship with particular model name (but fine in Rails 6.0)

I recently updated my app from Rails 6.0 to Rails 6.1 and ran into an error with a has_and_belongs_to_many relationship for a model called “EnabledDynamicField”. I can reproduce this issue in an isolated context in a new Rails 6.1 app.

In the reproduction case below, I’m including other models called “Apple” and “Zebra” to demonstrate that alphabetical sorting in derived join table names doesn’t seem to be related, and to show that a has_and_belongs_to_many relationship between the Apple and Zebra models, by comparison, doesn’t produce any errors.

Steps to reproduce

Create a new Rails 6.1 app (tested with Rails 6.1.4.1) and add following files:

# db/migrate/20210820050644_create_tables.rb
class CreateTables < ActiveRecord::Migration[6.0]
  def change
    create_table :apples do |t|
      t.timestamps
    end

    create_table :zebras do |t|
      t.timestamps
    end

    create_join_table :apples, :zebras do |t|
      t.index :apple_id, name: 'apple_id_app_zeb_join'
      t.index :zebra_id, name: 'zebra_id_app_zeb_join'
    end

    create_table :enabled_dynamic_fields do |t|
      t.timestamps
    end

    create_join_table :apples, :enabled_dynamic_fields do |t|
      t.index :apple_id, name: 'apple_id_app_edf_join'
      t.index :enabled_dynamic_field_id, name: 'enabled_dynamic_field_id_app_edf_join'
    end

    create_join_table :enabled_dynamic_fields, :zebras do |t|
      t.index :enabled_dynamic_field_id, name: 'enabled_dynamic_field_id_edf_zeb_join'
      t.index :zebra_id, name: 'zebra_id_edf_zeb_join'
    end
  end
end

# app/models/apple.rb
class Apple < ApplicationRecord
  has_and_belongs_to_many :enabled_dynamic_fields
  has_and_belongs_to_many :zebras
end

# app/models/zebra.rb
class Zebra < ApplicationRecord
  has_and_belongs_to_many :apples
  has_and_belongs_to_many :enabled_dynamic_fields
end

# app/models/enabled_dynamic_field.rb
class EnabledDynamicField < ApplicationRecord
  has_and_belongs_to_many :apples
  has_and_belongs_to_many :zebras
end

# test/models/habtm_join_test.rb
require "test_helper"

class HabtmJoinTest < ActiveSupport::TestCase
  test "Apple has_and_belongs_to_many Zebras relationship exists" do
    apple = Apple.create!
    assert apple.zebras.length == 0
  end

  test "Zebra has_and_belongs_to_many Apples relationship exists" do
    zebra = Zebra.create!
    assert zebra.apples.length == 0
  end

  test "Apple has_and_belongs_to_many EnabledDynamicFields relationship exists" do
    apple = Apple.create!
    assert apple.enabled_dynamic_fields.length == 0
  end

  test "EnabledDynamicField has_and_belongs_to_many Apples relationship exists" do
    enabled_dynamic_field = EnabledDynamicField.create!
    assert enabled_dynamic_field.apples.length == 0
  end

  test "Zebra has_and_belongs_to_many EnabledDynamicFields relationship exists" do
    zebra = Zebra.create!
    assert zebra.enabled_dynamic_fields.length == 0
  end

  test "EnabledDynamicField has_and_belongs_to_many Zebras relationship exists" do
    enabled_dynamic_field = EnabledDynamicField.create!
    assert enabled_dynamic_field.zebras.length == 0
  end
end

Run tests:

RAILS_ENV=test ./bin/rails db:migrate
./bin/rails test

If you create the above files and run the tests in a Rails 6.0 app, they all pass. If you do the same thing in Rails 6.1, a couple of tests fail – and to make matters worse it’s not always the same tests (and not always the same error messages). Here are some sample runs:

% ./bin/rails test
Running via Spring preloader in process 42365
Run options: --seed 63981

# Running:

..E

Error:
HabtmJoinTest#test_Apple_has_and_belongs_to_many_EnabledDynamicFields_relationship_exists:
ArgumentError: invalid byte sequence in UTF-8
    test/models/habtm_join_test.rb:16:in `block in <class:HabtmJoinTest>'


rails test test/models/habtm_join_test.rb:14

.E

Error:
HabtmJoinTest#test_Zebra_has_and_belongs_to_many_EnabledDynamicFields_relationship_exists:
ActiveRecord::StatementInvalid: SQLite3::SQLException: unrecognized token: """
    test/models/habtm_join_test.rb:26:in `block in <class:HabtmJoinTest>'


rails test test/models/habtm_join_test.rb:24

.

Finished in 0.304887s, 19.6794 runs/s, 13.1196 assertions/s.
6 runs, 4 assertions, 0 failures, 2 errors, 0 skips

And here’s another run where the same tests are failing, but the error message is different for one of them (changed from invalid byte sequence to unrecognized token:

% ./bin/rails test
Running via Spring preloader in process 42569
Run options: --seed 42091

# Running:

..E

Error:
HabtmJoinTest#test_Zebra_has_and_belongs_to_many_EnabledDynamicFields_relationship_exists:
ActiveRecord::StatementInvalid: SQLite3::SQLException: unrecognized token: "'enabled_dynamic_field_id"
    test/models/habtm_join_test.rb:26:in `block in <class:HabtmJoinTest>'


rails test test/models/habtm_join_test.rb:24

..E

Error:
HabtmJoinTest#test_Apple_has_and_belongs_to_many_EnabledDynamicFields_relationship_exists:
ActiveRecord::StatementInvalid: SQLite3::SQLException: unrecognized token: "'enabled_dynamic_field_id"
    test/models/habtm_join_test.rb:16:in `block in <class:HabtmJoinTest>'


rails test test/models/habtm_join_test.rb:14



Finished in 0.203738s, 29.4496 runs/s, 19.6331 assertions/s.
6 runs, 4 assertions, 0 failures, 2 errors, 0 skips

And here’s another run where only one test failed:

% ./bin/rails test
Running via Spring preloader in process 42484
Run options: --seed 5607

# Running:

...E

Error:
HabtmJoinTest#test_Apple_has_and_belongs_to_many_EnabledDynamicFields_relationship_exists:
ActiveRecord::StatementInvalid: SQLite3::SQLException: unrecognized token: """
    test/models/habtm_join_test.rb:16:in `block in <class:HabtmJoinTest>'


rails test test/models/habtm_join_test.rb:14

..

Finished in 0.253679s, 23.6519 runs/s, 19.7099 assertions/s.
6 runs, 5 assertions, 0 failures, 1 errors, 0 skips

And yet another run where there was only one failure:

% ./bin/rails test
Running via Spring preloader in process 42543
Run options: --seed 17029

# Running:

...E

Error:
HabtmJoinTest#test_Zebra_has_and_belongs_to_many_EnabledDynamicFields_relationship_exists:
ActiveRecord::StatementInvalid: SQLite3::SQLException: unrecognized token: """
    test/models/habtm_join_test.rb:26:in `block in <class:HabtmJoinTest>'


rails test test/models/habtm_join_test.rb:24

..

Finished in 0.205232s, 29.2352 runs/s, 24.3627 assertions/s.
6 runs, 5 assertions, 0 failures, 1 errors, 0 skips

Here’s some additional info output from the rails console (in Rails 6.1):

2.6.4 :001 > apple = Apple.create!
   (0.5ms)  SELECT sqlite_version(*)
  TRANSACTION (0.1ms)  begin transaction
  Apple Create (0.7ms)  INSERT INTO "apples" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2021-08-20 06:38:03.350455"], ["updated_at", "2021-08-20 06:38:03.350455"]]
  TRANSACTION (2.8ms)  commit transaction
 => #<Apple id: 2, created_at: "2021-08-20 06:38:03.350455000 +0000", updated_at: "2021-08-20 06:38:03.350455000 +0000">
2.6.4 :002 > apple.enabled_dynamic_fields.length == 0
Traceback (most recent call last):
        1: from (irb):2
ActiveRecord::StatementInvalid (SQLite3::SQLException: unrecognized token: "'")

And:

2.6.4 :001 > zebra = Zebra.create!
   (0.5ms)  SELECT sqlite_version(*)
  TRANSACTION (0.1ms)  begin transaction
  Zebra Create (0.5ms)  INSERT INTO "zebras" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2021-08-20 06:39:02.020261"], ["updated_at", "2021-08-20 06:39:02.020261"]]
  TRANSACTION (0.8ms)  commit transaction
 => #<Zebra id: 5, created_at: "2021-08-20 06:39:02.020261000 +0000", updated_at: "2021-08-20 06:39:02.020261000 +0000">
2.6.4 :002 > zebra.enabled_dynamic_fields.length == 0
Traceback (most recent call last):
        1: from (irb):2
ArgumentError (invalid byte sequence in UTF-8)

So there’s something that Rails doesn’t like about the EnabledDynamicField model.

Expected behavior

Since the above steps worked in Rails 6.0, I also expected them to work in Rails 6.1.

Actual behavior

Fine in Rails 6.0, errors in Rails 6.1.

System configuration

Rails version: 6.1.4.1 Ruby version: 2.6.4p104 Database adapter: sqlite3

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 15 (4 by maintainers)

Most upvoted comments

I wasn’t able to reproduce the error with your file, sorry. I’ve only been able to reproduce it in the context of a full Rails app.