Reputation: 167
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
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