1

I want to create a tree structure between two models, Bar and Foo.

Bar has many Foos

    Bar
  /  |  \
Foo Foo Foo
         ¦
        Bar
      /  |  \
    Foo Foo Foo

Bar can optionally belong to Foo.

Bar has many Foos, to infinity and beyond...

I configured things like this, but it doesn't seem to seed properly.

Even if I comment out any validations I have, I get the following error:

ActiveRecord::RecordInvalid: Validation failed: Foos bar must exist

I can't understand why.

class Bar < ApplicationRecord
  has_many :foos, inverse_of: :bar

  accepts_nested_attributes_for :foos
end


class Foo < ApplicationRecord
  belongs_to :bar, inverse_of: :foos

  accepts_nested_attributes_for :bar
end


class CreateFoos < ActiveRecord::Migration[6.1]
  def change
    create_table :foos do |t|
      t.text :description, null: false

      t.timestamps
    end
  end
end

class CreateBars < ActiveRecord::Migration[6.1]
  def change
    create_table :bars do |t|
      t.text :description

      t.references :foo,
        foreign_key: true,
        null: true,
        on_delete: :cascade,
        on_update: :cascade

      t.timestamps
    end
  end
end

class AddBarIdToFoosTable < ActiveRecord::Migration[6.1]
  def change
    add_reference :foos,
      :bar,
      foreign_key: true,
      null: false,
      on_delete: :cascade,
      on_update: :cascade
  end
end


Bar.create!([
  {
    description: 'Lorem ipsum...',
    foos_attributes: [
      {
        description: 'Lorem ipsum...',
        bar_attributes: {
          description: 'Lorem ipsum...',
          foos_attributes: [
            {
              description: 'Lorem ipsum...',
              bar_attributes: {
                description: 'Lorem ipsum...',
                foos_attributes: [
                  {
                    description: 'Lorem ipsum...'
                  },
                  {
                    description: 'Lorem ipsum...'
                  },
                  {
                    description: 'Lorem ipsum...'
                  },
                  {
                    description: 'Lorem ipsum...'
                  }
                ]
              }
            }
          ]
        }
      }
    ]
  }
])
Jonathan
  • 10,936
  • 8
  • 64
  • 79
  • why do you need accepts_nested_attributes in Foo? to create a foo with bar just pass the bar_id to foo – Mosaaleb Nov 18 '21 at 23:46
  • This logic feels really circular and I'm struggling to understand what the real-world application would be here. Why would you use two different models to define the same object (i.e., a node in your tree)? IMO every element (foo or bar) should be of your node model. Here is a random blog that you might find helpful: https://www.leighhalliday.com/tree-structures-in-your-rails-models – Allison Nov 18 '21 at 23:49
  • @Allison what you're seeing here is not a one-to-one mapping of what I'm trying to do. The models involved have their particular distinctions I need to keep separate – Jonathan Nov 19 '21 at 00:57
  • @Mosaaleb yeah that's a good point, I'm not most familiar with Rails, coming over from another framework so I'm not completely clued up yet, I'll try removing this among other modifications – Jonathan Nov 19 '21 at 00:58
  • 1
    Deleted my comment in favor of answering before I saw this, but if you have a strict parent child relationship you may want to define both a `belongs_to` and `has_many` on both classes, and the Rails Guides are pretty good if you haven't come across them before: https://guides.rubyonrails.org/association_basics.html – Allison Nov 19 '21 at 02:06
  • No worries~ I'm going with belongs_to, has_many in one direction and belongs_to, has_one in the other in my situation – Jonathan Nov 19 '21 at 02:15

1 Answers1

2

ActiveRecord::RecordInvalid: Validation failed: Foos bar must exist

  • This is telling you that one of your Foo declarations requires the presence of bar
  • The reference to bar in your model declaration for Foo is in the belongs_to association
  • belongs_to is presence-validated by default in certain versions of rails; changing belongs_to :bar to belongs_to :bar, optional: true will likely resolve your issue

Ref: https://github.com/rails/rails/pull/18937/files

Allison
  • 1,925
  • 1
  • 18
  • 25