Rails - 非持久父级需要找到候选子级并将它们分配给自己。然后显示“新”形式

发布于 2024-11-29 02:16:40 字数 4791 浏览 1 评论 0原文

这段代码显示了我想要做的事情,但当然不会工作,因为父级还没有 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

薄荷梦 2024-12-06 02:16:40

我将在账单上创建一个新的“创建者”方法,该方法返回附有关联交易的新账单。类似于:

def self.NewWithTransactions
    bill = Bill.new
    bill_transactions = find_candidate_transactions
    bill
end

然后从控制器的新操作中,只需执行以下操作:

bill = Bill.NewWithTransactions

将其扔回您的视图,您应该能够创建新的账单,并在提交时附加交易。如果这不起作用,您可能必须按照评论者之一的建议进行操作,将不关联的事务发送到您的视图,并在创建操作中重新关联它们。

I would create a new "creator" method on Bill that returns a new bill with associated transactions attached. Something like:

def self.NewWithTransactions
    bill = Bill.new
    bill_transactions = find_candidate_transactions
    bill
end

Then from your controller's new action, just do:

bill = Bill.NewWithTransactions

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文