Aloalo
Aloalo

Reputation: 167

Before validation on nested model

I have a nested model items and I am trying to multiply two columns together cost and quantity to set the last column price. I need to set the column price before the form is saved and i need to also validate the model. The before_validation call back obviously breaks if the cost and quantity is not set before saving. Any ideas of how I can multiply these columns together and also validate the model?

Here is my code item.rb

class Item < ActiveRecord::Base
belongs_to :invoice

 validates :invoice_id, presence: true
 validates :name, presence: true
 validates_presence_of :quantity, :cost, :price

 before_validation :set_price

 def set_price
    self.price = cost * quantity.round(2)
 end
end

and here is my parent model invoice.rb

class Invoice < ActiveRecord::Base
belongs_to :user
has_many :items

accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true

validates :sender, presence: true
validates_associated :items

before_save :increment_invoice_number, :set_amount

private

def increment_invoice_number
    if published == true
        self.invoice_number = user.invoices.where(:published => true).count + 1
    end
end

def set_amount
    self.amount = items.map(&:price).sum             
end

end

Upvotes: 3

Views: 2356

Answers (1)

odaata
odaata

Reputation: 306

If you're always going to update the price on every save, you don't need to validate it's presence, so just remove that from your validations and add a before_save callback, which will get called after the validations, so you'll be guaranteed to have valid cost and quantity fields. Here's a link to the place in the Rails Guides where this is discussed: http://guides.rubyonrails.org/active_record_callbacks.html#available-callbacks

Here's an example:

class Item < ActiveRecord::Base
  belongs_to :invoice

  validates :invoice_id, presence: true
  validates :name, presence: true
  validates_presence_of :quantity, :cost

  before_save :set_price

  def set_price
    self.price = cost * quantity.round(2)
  end
end

If you want to set the price only on creation or update, you can use the before_create or before_update callback instead. Alternatively, you can optionally set the price if it isn't already set:

self.price ||= cost * quantity.round(2)

Is this what you are trying to achieve?

Update: After some discussion (see comments), it seems like the best way to handle this might be to update all child items in a before_save callback on the parent invoice, since this will be called every time an invoice is saved. Below is an example implementation - I also removed a few validates statements from your original code and combined everything using the new syntax. Also, the items will be validated regardless when updated through a nested_attributes association.

class Item < ActiveRecord::Base
  belongs_to :invoice

  validates :invoice_id, :name, :quantity, :cost, presence: true

  def set_price
    self.price = cost * quantity.round(2)
  end
end

class Invoice < ActiveRecord::Base
  belongs_to :user
  has_many :items

  accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true

  validates :sender, presence: true

  before_save :increment_invoice_number, :update_prices, :set_amount

  private

  def increment_invoice_number
    if published == true
      self.invoice_number = user.invoices.where(:published => true).count + 1
    end
  end

  def update_prices
    items.each(&:set_price)
  end

  def set_amount
    self.amount = items.map(&:price).sum             
  end
end

Upvotes: 4

Related Questions