20

I have a couple of models like so

class Bill < ActiveRecord::Base
  has_many :bill_items
  belongs_to :store

  accepts_nested_attributes_for :bill_items
end

class BillItem <ActiveRecord::Base
  belongs_to :product
  belongs_to :bill

  validate :has_enough_stock

  def has_enough_stock
    stock_available = Inventory.product_is(self.product).store_is(self.bill.store).one.quantity
    errors.add(:quantity, "only #{stock_available} is available") if stock_available < self.quantity
  end
end

The above validation so obviously doesn't work because when I'm reading the bill_items from nested attributes inside the bill form, the attributes bill_item.bill_id or bill_item.bill are not available before being saved.

So how do I go about doing something like that?

1
  • I solved this by adding a call back to the association, :before_add => :set_nest Commented Feb 19, 2011 at 6:35

5 Answers 5

18

This is how I solved it eventually; by setting parent on callback

  has_many :bill_items, :before_add => :set_nest

private
  def set_nest(bill_item)
    bill_item.bill ||= self
  end
Sign up to request clarification or add additional context in comments.

1 Comment

Yeah! It has been bugging me lately. I wish it was automatic, one could write a more generic version if callback functions were run against the association proxy (like association extensions) instead of the association owner.
9

In Rails 4 (didn't test on earlier versions) you can access the parent model by setting the inverse_of option on has_many or has_one:

class Bill < ActiveRecord::Base
  has_many :bill_items, inverse_of: :bill
  belongs_to :store

  accepts_nested_attributes_for :bill_items
end

Documentation: Bi-directional associations

1 Comment

This is true answer for rails 4
1

The bill_item.bill should be available , you could try to do a raise self.bill.inspect to see if it's there or not, but i think the problem is elsewhere.

1 Comment

Even if it isn't available, the OP could just add a before_validation callback to set it. The BillItems shouldn't need to know their own IDs or the parent Bill's ID to validate.
1

I "fixed" it by setting parent in a callback:

class Bill < ActiveRecord::Base
  has_many :bill_items, :dependent => :destroy, :before_add => :set_nest
  belongs_to :store

  accepts_nested_attributes_for :bill_items

  def set_nest(item)
    item.bill ||= self
  end
end

class BillItem <ActiveRecord::Base
  belongs_to :product
  belongs_to :bill

  validate :has_enough_stock

  def has_enough_stock
        stock_available = Inventory.product_is(self.product).store_is(self.bill.store).one.quantity
    errors.add(:quantity, "only #{stock_available} is available") if stock_available < self.quantity
  end
end

The set_nest method did the trick. Wish it came standard with accepts_nested_attributes_for.

Comments

0

Yeah, this kind of problem can be annoying. You could try adding a virtual attribute to your Bill Item model like this:

class BillItem <ActiveRecord::Base
  belongs_to :product
  belongs_to :bill

  attr_accessible :store_id

  validate :has_enough_stock

  def has_enough_stock
   stock_available = Inventory.product_is(self.product).store_is(load_bill_store).one.quantity
   errors.add(:quantity, "only #{stock_available} is available") if stock_available < self.quantity
  end

  private

  def load_bill_store
    Store.find_by_id(self.store_id)
  end
end

And then in your view you could add a hidden field like this:

<%= bill_item.hidden_field :store_id, :value => store_id %>

This hasn't been test but it might work. You might not find it desirable to have the store_id in the html but it may not be a concern. Let me know if this helps.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.