Ruby design pattern: holding multiple types of objects in one class
I am building a double-entry accounting system in Rails, which includes:
- User. Attributes: user_id. Relation: has_many: accounts
- Account. Attributes: user_id. Relation: belongs_to :user, postings
- Journal. Attributes: id. Relations: has_many :posting, belongs_to :period
- Posting. Attributes: journal_id, account_id, points. Relations: belongs_to :journal, :account.
For each transaction from account 1 to account 2, there will be two postings, one on account 1 and one on account 2, and one journal object to describe the entire transaction.
The main method in account is:
def transfer (destination, amount, description=nil)
Journal.transfer self, destination, amount, description
end
which will call transfer method from Journal to generate Postings:
def self.transfer( source, destination, amount, description=nil)
transaction do
j = create! :description => description, :period_id => p.id
Posting.create! :journal_id => j.id, :account_id => source.id,
:points => -Integer(amount)
Posting.create! :journal_id => j.id, :account_id => destination.id,
:points => Integer(amount)
See more details about this system here: http://homepages.tcp.co.uk/~m-wigley/gc_wp_ded.html
Now, the twist is I want to allow account 1 to "pledge" some points to account 2. By pledging, account 1 will no longer has access to those points. If later on, account 1 authorizes the transaction, then account 2 can use those points.
I can only think of one way to go about this (learning from this discussion):
Create two accounts for each user, "settled" and "pledged". When 1 pledges, points are transferred from 1's settled account to 2's pledged account. When 1 approves, points will be transferred from 2's pledged account to settled account.
To implement this, I add a column "account_t开发者_运维问答ype" to Account, enum with "pledged" and "settled" However, it doesn't look right because when user wants to make a pledge, he has to find out from which account type he should send point from, and to which type of account he should send to. Because of this change, account now can't make transfer decision on behalf of user as before, because there are multiple accounts for each user.
Now, I can add transfer functionality to User class, which then will select account_type he has, then send points from that account. But I don't want tight couple between financial system and general part of my system.
I can add a "wrapper" class AccountManager, which has one-to-one relationship with User, who can choose which account to send point from. But it seems a bit unnecessarily complicated.
I can also add method classes for transfer, settle, which will lookup for appropriate account types to make transfer. But it doesn't look right either.
I don't think I can revise Posting so that it can handle this smoothly, but I may be wrong.
So I would like to have your advice on how should I go about this problem.
Thank you.
Users don't really have two accounts (or account types) but rather transfers that are pending or completed. When I worked in banking (specifically credit/debit card payment processing) everything is handled this way because some funds are transferred via batch files every n hours.
The nutshell version is that there's an intermediate step. The initial payment removes money from 1's account. The funding request is marked pending until 2's bank gets the funding request and processes it. Once it's processed, the funding req is completed, and everything is as it should be.
If for some reason there's a refund or whatever, the funding request is cancelled and the funds are returned to 1.
This process occurs asynchronously (obviously) but lives alongside real-time requests. In our system is was represented in the same way, but it wouldn't need to be--in your case you could handle direct transfers as you do now, and handle pledges using a two-step process.
In our system the funding request processing acted as the journal--we could re-create/track the path of any payment by looking at funding request update history. I'm not sure what the best way to handle it in your system would be; my initial guess would be to have a journal entry for the withdrawal and one for the acceptance, and have status that indicated they were part of a two-stage transfer.
Not sure how much this helps, but it's one way this kind of scenario is handled in real-life (more or less).
精彩评论