mennayahia
mennayahia

Reputation: 25

Ruby on Rails | How to insert new row into database table that has three foreign keys

I am using Ruby 2.6.3

I have four tables:

Table #1: Transaction (id, title)

Table #2: Trip (id, name)

Table #3: TripBreakdown (id, name)

Table #4: TripTransaction (id, transaction_id, trip_id, trip_breakdown_id)

Each transaction can belong to one trip and one tripBreakdown. One Trip has many transactions and one TripBreakdown has many transactions So each Transaction belongs to zero or more Trip and TripBreakdown.

For that I created Table #4 to store the transaction_id, trip_id and trip_breakdown_id

I want to create the TripTransaction table so that (transaction_id, trip_id, trip_breakdown_id) are all foreign keys.

This is what the Model classes look like:

class Transaction < ApplicationRecord
  belongs_to :trip_transaction, optional: true
end
class Trip < ApplicationRecord
    has_many :trip_transactions, class_name: "TripTransaction", foreign_key: "trip_transaction_id"
end
class TripBreakdown < ApplicationRecord
    has_many :trip_transactions, class_name: "TripTransaction", foreign_key: "trip_transaction_id"
end
class TripTransaction < ApplicationRecord
    belongs_to :transactions, class_name: "Transaction", foreign_key: "transaction_id"
    belongs_to :trips,    class_name: "Trip", foreign_key: "trip_id"
    belongs_to :trip_breakdowns,    class_name: "TripBreakdown", foreign_key: "trip_breakdown_id"
end

Questions: 1) I am not sure if my Model classes are correct or not?

2) How can I create new TripTransaction object, connect it with three foreign keys and insert it in the database?

If I have one foreign key, the documentation says I can do something like @book = @author.books.create(published_at: Time.now) but how can I do that in my case? Thanks

Upvotes: 0

Views: 899

Answers (2)

catmal
catmal

Reputation: 1758

Answer 1: You don't need a join table (trip_transactions) since transaction has one trip and one trip breakdown. Your table transactions will have column trip_id and trip_breakdown_id. If they can be nil you set optional: :true in relationship.

You can simplify to:

class Transaction < ApplicationRecord
  belongs_to :trip, optional: :true
  belongs_to :trip_breakdown, optional: :true
end

class Trip < ApplicationRecord
    has_many :transactions
end

class TripBreakdown < ApplicationRecord
    has_many :transactions
end

Answer 2: Can be done in many ways depending on how you are creating the record. An example to give you an idea is:

@new_transaction = @trip.transactions.new
@new_transaction.trip_breakdown_id = @trip_breakdown.id

@new_transaction.save

Upvotes: 2

Schwern
Schwern

Reputation: 164829

@Catmal has the models right. I'd add renaming the generic Transaction to a more specific TripTransaction and to match the other related class names.

Creation can be done in several ways. These all assume you already have Trip and TripBreakdown objects. These all basically do the same thing, but there are subtle differences.

# Via Transaction.create!
transaction = Transaction.create!(
  trip: trip,
  trip_breakdown: trip_transaction
)

# Via Trip's transaction association.
transaction = trip.transactions.create!(
  trip_breakdown: trip_transaction
)

# Via TripBreakdown's transaction association.
transaction = trip_breakdown.transactions.create!(
  trip: trip
)

They'll make a Transaction object associated with the Trip and TripBreakdown. Using the ! version of create means they'll raise an exception if there's a problem instead of silently returning nil.

The advantage of using the association is the cached association will be updated. For example, if you were to use Transaction.create! you might wind up with an out of date cache.

# Load the Trip's transactions into memory
p trip.transactions

transaction = Transaction.create!(
  trip: trip,
  trip_breakdown: trip_transaction
)

# The Trip's transactions were cached. This will not include
# the new transaction.
p trip.transactions

# Now the new transaction is seen.
p trip.transactions.reload

If you instead create via the association the cached associations will be updated.

# Load the Trip's transactions into memory
p trip.transactions

# Create an associated Transaction and update the association.
trip.transactions.create!(
  trip_breakdown: trip_transaction
)

# This will include the new Transaction.
p trip.transactions

See ActiveRecord::Associations::ClassMethods for more.

Upvotes: 1

Related Questions