2
votes

I have payments with one transaction associated with each payment. The transactions can be associated with payments, deposits, etc. so I need a :through relation.

The Payment class:

class Payment < ActiveRecord::Base
  has_many :payment_transactions
  has_many :transactions, :through => :payment_transactions
end

And the Transaction class:

class Transaction < ActiveRecord::Base
  has_one :payment_transaction, :inverse_of => :transaction
  has_one :payment, :through => :payment_transaction
end

And finally the PaymentTransaction class:

class PaymentTransaction < ActiveRecord::Base
  belongs_to :payment
  belongs_to :transaction
end

When adding the transaction to the payment I would like to be able to access the parent information (the payment attributes) from within the newly created transaction:

2.1.0 :001 > p = Payment.new
 => #<Payment id: nil, state: "new", created_at: nil, updated_at: nil>
2.1.0 :002 > p.save
 => true
2.1.0 :003 > p.transactions.build()
 => #<Transaction id: nil, myid: nil, parent_class_is: nil, created_at: nil, updated_at: nil>
2.1.0 :004 > p.transactions.first.payment
 => nil

I am looking for a way to access the payment from the newly created transaction associated with the payment - but no luck. The payment is saved, however the transaction is still not committed to the database. Only when the payment is saved and the associated transaction is stored I get access to the parent object:

2.1.0 :005 > p.save
2.1.0 :008 > p.reload
2.1.0 :009 > p.transactions.first.payment
=> #<Payment id: 3, state: "new", created_at: "2014-01-28 09:23:13", updated_at: "2014-01-28 09:23:13">

Using the :inverse_of is not possible with :through associations. :(


Edit 1:

Tried to access the parent via a proxy association:

app/concerns/transaction_proxy.rb

module TransactionProxy

  private

  def my_owner
    proxy_association.owner
  end

end

The payment model:

has_many :payment_transactions
has_many :transactions, -> { extending TransactionProxy }, :through => :payment_transactions

Loading the payment:

2.1.0 :006 > p = Payment.first
  Payment Load (0.2ms)  SELECT "payments".* FROM "payments" ORDER BY "payments"."id" ASC LIMIT 1
 => #<Payment id: 1, state: "new", created_at: "2014-01-28 12:31:11", updated_at: "2014-01-28 12:31:11">

Setting the transaction:

2.1.0 :007 > p.transactions.build()
 => #<Transaction id: nil, myid: nil, parent_class_is: nil, created_at: nil, updated_at: nil>

Query the transaction:

2.1.0 :005 > p.transactions.first
 => #<Transaction id: nil, myid: nil, parent_class_is: nil, created_at: nil, updated_at: nil>

2.1.0 :007 > p.transactions.first.payment
 => nil

and trying to use the proxy:

2.1.0 :006 > p.transactions.first.my_owner
NoMethodError: undefined method `my_owner' for #<Transaction:0x00000103e44450>

I assume that the .my_owner on a transaction should return the owner (payment) of that transaction?

1
can you add a little context to why - because there's probably an alternative simpler solution than doing what you're asking...Richard Jordan
The transaction needs to pull some information from the parent object (a description) and store that in an attribute in the transaction. Also if the transaction got a payment it should perform certain business logic (again a transaction can belong to a payment, a deposit, etc.)Lasse Laursen
You could wrap it in a DB transaction, that will enable you to do the save and still roll everything back if neededCarsten Gehling
Before you save p again: What does p.transactions.first.payment_transaction.inspect return?Carsten Gehling
p.transactions.first.payment_transaction.inspec returns nil - so the association through the payment_transactions table is not defined yet.Lasse Laursen

1 Answers

0
votes

Your code looks confusing to me!


Polymorphic

I think you'd benefit from a polymorphic association on the transactions model. This will allow your transactions to belong to either payments, deposits or any model

Your use of a through relation means transactions can only be associated with payments and transaction & payment objects have to be created before you can associate them:

class Transaction < ActiveRecord::Base
  belongs_to :transactable, polymorphic: true
end

class Payment < ActiveRecord::Base
  has_many :transactions, as: :transactable
end

class Deposit < ActiveRecord::Base
  has_many :transactions, as: :transactable
end

class Reward < ActiveRecord::Base
  has_many :transactions, as: :transactable
end

Polymorphic Association


Extrabutes

If you want to keep your has_many :through association, you can use the ActiveRecord Association Extensions proxy_association object to retrieve the data you need

We made a script to extra join-table data, which you'll be able to adapt to pull child-object attributes:

#app/models/concerns/image_caption.rb
module ImageCaption

        #Load
        def load
      captions.each do |caption|
              proxy_association.target << caption
      end
        end

        #Private
        private

        #Captions
        def captions
                return_array = []
                through_collection.each_with_index do |through,i|
                        associate = through.send(reflection_name)
                        associate.assign_attributes({caption: items[i]})
                        return_array.concat Array.new(1).fill( associate )
                end
                return_array
        end

        #######################
        #      Variables      #
        #######################

        #Association
        def reflection_name
                proxy_association.source_reflection.name
        end

        #Foreign Key
        def through_source_key
                proxy_association.reflection.source_reflection.foreign_key
        end

        #Primary Key
        def through_primary_key
                proxy_association.reflection.through_reflection.active_record_primary_key
        end

        #Through Name
        def through_name
                proxy_association.reflection.through_reflection.name
        end

        #Through
        def through_collection
                proxy_association.owner.send through_name
        end

        #Captions
        def items
                through_collection.map(&:caption)
        end

        #Target
        def target_collection
                #load_target
                proxy_association.target
        end

end

#app/models/message.rb
has_many :image_messages
has_many :images, through: :image_messages, extend: ImageCaption

If you'd like me to modify this code for you, please ask in the comments!