Search code examples
ruby-on-railsruby-on-rails-4parent-childattr-accessor

Rails attr_accessor attribute on parent model available in children


Context:

Each Order has many Items & Logistics. Each Item & Logistic (as well as the Order itself) have many Revenues.

I am creating Order + Items & Logistics at once using an accepts_nested_attributes_for on Order. However, Revenues gets created using an after_create callback on each of the models Order, Item, and Logistics. Why? Because given the difference in interpretation in these models, the code reads cleaner this way. (But if this way of doing it is what's causing this question to be asked, I will obviously reconsider!)

One key attribute that I need to store in Revenues is pp_charge_id. But pp_charge_id is not something that either Order, Items, or Logistics needs to worry about. I've attached an attr_accessor :pp_charge_id to Order, so that one works fine, however, once I'm in the child Items or Logistics models, I no longer have access to pp_charge_id which again I need to save an associated Revenue. How should I do this?

Controller Code:

@order = Order.new(params) #params includes Order params, and nested params for child Item & Logistics
@order.pp_charge_id = "cash"
@order.save #I need this to not only save the Order, the children Item & Logistics, but then to also create the associated Revenue for each of the aforementioned 3 models

ORDER Model Code:

has_many :items
has_many :revenues

attr_accessor :pp_charge_id
after_create :create_revenue

def create_revenue
  self.revenues.create(pp_charge_id: self.pp_charge_id)
end

#This WORKS as expected because of the attr_accessor

ITEM/ LOGISTIC model code:

has_many :revenues
belongs_to :order

after_create :create_revenue

def create_revenue
  self.revenues.create(pp_charge_id: self.order.pp_charge_id)
end

 #This DOES NOT work because self.order.pp_charge_id is nil

ORDER model code:

belongs_to :order
belongs_to :item
belongs_to :logistic

Again I understand the attr_accessor is not designed to persist across a request or even if the Order itself is reloaded. But it also doesn't make sense to save it redundantly in a table that has no use for it. If the only way to do this is to put the pp_charge_id into the params for the order and save everything all at once (including Revenues), then let me know because I know how to do that. (Again, would just rather avoid that because of how it's interpreted: params are coming from User, Revenue data is something I'm providing)


Solution

  • I think if you want the order's pp_charge_id to apply to all its items and logistics, I'd put all that into the order's after_create callback:

    # order.rb
    def create_revenue
      revenues.create(pp_charge_id: pp_charge_id)
      items.each {|i| i.revenues.create(pp_charge_id: pp_charge_id)}
      logistics.each {|l| l.revenues.create(pp_charge_id: pp_charge_id)}
    end
    

    EDIT: Alternately, you could add inverse_of to your belongs_to declarations, and then I believe Item#create_revenue would see the same Order instance that you set in the controller. So if you also added an attr_accessor to the Item class, you could write its create_revenue like this:

    # item.rb
    def create_revenue
      revenues.create(pp_charge_id: pp_charge_id || order.pp_charge_id)
    end
    

    This should cover the new requirement you've mentioned in your comment.