ruby-on-railsmodel

How can I make a model belong_to two times the same model?


I have a model Entity that can have offers and invoices. The user can create custom mumber formats to display the invoice number. For example the internal invoice number 23 could become 2025INV00000023 or something like that. Now, for offers, we need to be able to use a different number format. 23 there could become 2025OFF00000023.

I have done this by creating a NumberFormat class where the user can configure number formats in several ways. Number formats are attached to entities because all invoices from an entity need to have uniform/consistent numbering for tax/accounting purposes.

My Entity model starts like this:

class Entity < ApplicationRecord
  has_many :invoices, dependent: :restrict_with_error
  has_many :offers, dependent: :restrict_with_error

some more unrelated has_many

  belongs_to :invoice_number_format, class_name: 'NumberFormat'
  belongs_to :offer_number_format, class_name: 'NumberFormat'

Now my NumberFormat class needs to have the other end of the belongs_to relationship. But I'm not sure if the doubling there is the correct approach in order to have a dependent: :restrict_with_error preventing the user from deleting number formats that are still in use. VSCode's autoformatter just deletes the second one on save. I can of course save without formatting (and take great care to not forget whenever editing this file) and after preliminary testing it seems like it works but I'm wondering if my approach is maybe not ideal?

  has_many :entities, inverse_of: :invoice_number_format,
                      dependent: :restrict_with_error
  # This line gets deleted on save
  has_many :entities, inverse_of: :offer_number_format,
                      dependent: :restrict_with_error

Solution

  • You can have any number of associations pointing to the same model but each association needs to have a unique name or you will just overwrite the previous definition and it's methods.

    class Entity < ApplicationRecord
      belongs_to :invoice_number_format, 
        class_name: 'NumberFormat'
        inverse_of: :entities_as_invoice_number_format
      belongs_to :offer_number_format, 
        class_name: 'NumberFormat
        inverse_of: :entities_as_offer_number_format
    end
    
    class NumberFormat < ApplicationRecord
      has_many :entities_as_invoice_number_format, 
        inverse_of: :invoice_number_format,
        dependent: :restrict_with_error,
        foreign_key: :invoice_number_format_id,
        class_name: 'Entity'
      has_many :entities_as_offer_number_format, 
        inverse_of: :offer_number_format,
        dependent: :restrict_with_error,
        foreign_key: :offer_number_format_id,
        class_name: 'Entity'
    end
    

    The foreign_key and class_name options are required here as they are derived from the name of the assocation.