hexinpeter
hexinpeter

Reputation: 1570

What is the best way to model a `has_two` relationship in Rails

I have a Contract model, each of which has two Payment (prepayment and second_payment).

I know one possible way to do so may be as follow:

class Contract < ActiveRecord::Base
  belongs_to :prepayment, :class_name => "Payment"
  belongs_to :second_payment, :class_name => "Payment"
end


## Schema 
create_table "contracts" do |t|
  t.integer  "prepayment_id"
  t.integer  "second_payment_id"
end

But in a logical sense, it really doesn't sound right for a contract belonging to a payment, or a payment has a contract. Should be the other way round.

What is the best practice to model this kind of relationship?

Upvotes: 1

Views: 164

Answers (3)

Lorenzo Sinisi
Lorenzo Sinisi

Reputation: 470

I would rather use Payment model association normal as it would be and than add a category to Payment that can be 0 for "prepayment" and 1 for "payment". In that way in the future you could also have other type of payments just by adding another category and you don't need to modify the association every time.

class Contract < ActiveRecord::Base
  has_many :payments
end

class Payment < ActiveRecord::Base
  enum category: [ :payment, :prepayment ]
end

## Schema 
create_table "prepayment" do |t|
  t.integer  "category"
end

Upvotes: 1

Richard Peck
Richard Peck

Reputation: 76774

I'd do this:

#app/models/contract.rb
class Contract < ActiveRecord::Base
   has_many :payments, -> { where(category: [0,1]) } #-> only returns the payments where category is "prepayment" or "secondary_payment"
end

#app/models/payment.rb
class Payment < ActiveRecord::Base
   belongs_to :contract
   enum category: [:prepayment, :secondary_payment]

   validates :category, uniqueness: { scope: :contract_id } #-> only one of each category per contract

   scope :pre,       -> { find_by category: 0 }
   scope :secondary, -> { find_by category: 1 }
end

This way, you'll be able to use the following:

@contract = Contract.find(1)

@contract.payments.pre
@contract.payments.secondary

Upvotes: 0

Alexander Shlenchack
Alexander Shlenchack

Reputation: 3869

A logic way would be to use this (simple realization):

class Contract < ActiveRecord::Base
  has_many :payment_transactions, as: :transactionable
end

class PaymentTransaction < ActiveRecord::Base
  belongs_to :transactionable, polymorphic: true
end

For PaymentTransaction you can use enum field:

enum payment_type: [:first_income, :expense]

Upvotes: 0

Related Questions