Rails - 非持久父级需要找到候选子级并将它们分配给自己。然后显示“新”形式
这段代码显示了我想要做的事情,但当然不会工作,因为父级还没有 id:
class Parent < ActiveRecord::Base
has_many :children
after_initialize :find_children, :if => Proc.new {|parent| parent.new_record?}
private
def find_children
Child.where("blah blah blah").each do |child|
child.parent = self
#etc, etc, etc
end
end
end
几乎就像我的控制器的“新”操作需要在显示新表单之前保存父级一样。这感觉不对。解决这个问题的好方法是什么?
更新
在我的具体情况下,子对象是 BillTransactions(考虑费用和积分),父对象是 Bills。在整个计费周期内,这些交易都会在帐户中累积。在计费周期结束时,用户创建给定周期的账单,因此需要账单在创建时找到其子账单。
在发布问题后,我一直在思考这个问题。由于 Bill 和 BillTransactions 可以存在于许多不同的状态(待处理、草稿、活动、通过电子邮件发送等),我将使用状态机来管理对象的生命周期。到目前为止,这就是我的想法:
class Bill < ActiveRecord::Base
belongs_to :account
has_many :bill_transactions
attr_accessible :account, :bill_period_start, :bill_period_end
after_initialize :find_fees, :if => Proc.new {|bill| bill.new_record?}
validates_presence_of :account, :bill_period_start, :bill_period_end
state_machine :initial => :pending do
after_transition :pending => :canceled, :do => :destroy_self
before_transition :active => :emailed, :do => :email_bill
event :save_draft do
transition :pending => :draft
end
event :activate do
transition [:pending, :draft] => :active
end
event :email do
transition :active => :emailed
end
event :apply_payment do
transition [:active, :emailed] => :partial
transition [:active, :emailed, :partial] => :paid
end
event :cancel do
transition [:pending, :draft] => :canceled
end
end
private
def find_fees
self.save
unless [account, bill_period_start, bill_period_end].any? {|attr| attr.nil? }
BillTransaction.where(:account_id => account.id, :transaction_date => bill_period_start..bill_period_end, :transaction_type => BillTransaction::TRANS_TYPES['Fee']).each do |fee|
fee.stage self
end
end
end
def destroy_self
self.bill_transactions.each do |trans|
trans.unstage
end
self.destroy
end
end
因此,在第一次初始化账单后,它基本上会保存自身,找到所有相关交易,然后“暂存”它们。这意味着 BillTransaction 的状态设置为 staged(如果新账单被销毁,则可以转换回未计费),并且其 bill_id 设置为当前账单的 id。您可以看到,如果处于待处理状态的账单被取消,则所有交易都将取消暂存(返回到未账单状态)。
此解决方案的问题在于,向 BillsController#new 发送 GET 请求应该是幂等的。该解决方案并不是严格幂等的,我很难了解如何确保在用户离开新表单时回滚服务器的状态。
我正在走一条痛苦的道路吗?
This code shows what I'd like to do, but of course won't work because the Parent does not yet have an id:
class Parent < ActiveRecord::Base
has_many :children
after_initialize :find_children, :if => Proc.new {|parent| parent.new_record?}
private
def find_children
Child.where("blah blah blah").each do |child|
child.parent = self
#etc, etc, etc
end
end
end
It's almost as if my controller's "new" action needs to save the Parent before displaying the new form. This doesn't feel right. What is a good approach to this problem?
Update
The child objects in my specific case are BillTransactions (think fees and credits) and the parents are Bills. Throughout a billing period, these transactions are accrued on an Account. At the end of the billing period, the user creates a bill for a given period, hence the need for a bill to find its children when it's created.
I've been thinking about this some more after I posted the question. Since the Bill and BillTransactions can exist in many different states (pending, draft, active, emailed, etc) I'm going to use a state machine to manage the object's lifecycle. So far this is what I've come up with:
class Bill < ActiveRecord::Base
belongs_to :account
has_many :bill_transactions
attr_accessible :account, :bill_period_start, :bill_period_end
after_initialize :find_fees, :if => Proc.new {|bill| bill.new_record?}
validates_presence_of :account, :bill_period_start, :bill_period_end
state_machine :initial => :pending do
after_transition :pending => :canceled, :do => :destroy_self
before_transition :active => :emailed, :do => :email_bill
event :save_draft do
transition :pending => :draft
end
event :activate do
transition [:pending, :draft] => :active
end
event :email do
transition :active => :emailed
end
event :apply_payment do
transition [:active, :emailed] => :partial
transition [:active, :emailed, :partial] => :paid
end
event :cancel do
transition [:pending, :draft] => :canceled
end
end
private
def find_fees
self.save
unless [account, bill_period_start, bill_period_end].any? {|attr| attr.nil? }
BillTransaction.where(:account_id => account.id, :transaction_date => bill_period_start..bill_period_end, :transaction_type => BillTransaction::TRANS_TYPES['Fee']).each do |fee|
fee.stage self
end
end
end
def destroy_self
self.bill_transactions.each do |trans|
trans.unstage
end
self.destroy
end
end
So after a Bill is initialized for the first time, it basically saves itself, finds all relevant transactions, and "stages" them. This means BillTransaction's state is set to staged (which can transition back to unbilled if the new bill is destroyed) and its bill_id is set to the current Bill's id. You can see that if a Bill in the pending state is canceled, all of the transactions are unstaged (returned to the unbilled state).
The problem with this solution is that sending a GET request to BillsController#new is supposed to be idempotent. This solution isn't strictly idempotent and I'm having a hard time seeing how I can ensure that the server's state will be rolled back if the user navigates away from the new form.
Am I heading down a painful path here?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我将在账单上创建一个新的“创建者”方法,该方法返回附有关联交易的新账单。类似于:
然后从控制器的新操作中,只需执行以下操作:
将其扔回您的视图,您应该能够创建新的账单,并在提交时附加交易。如果这不起作用,您可能必须按照评论者之一的建议进行操作,将不关联的事务发送到您的视图,并在创建操作中重新关联它们。
I would create a new "creator" method on Bill that returns a new bill with associated transactions attached. Something like:
Then from your controller's new action, just do:
Throw that back to your view and you should be able to create the new bill with transactions attached when submitted. If that doesn't work, you probably have to do as one of the commentors suggested and send the unassociated transactions to your view and reassociate them in the create action.