I have an e-commerce application that is exhibiting strange behavior. The product is a training class. The student creates reservations for various classes, which are added as line-items to a cart. At some point, and order is created and paid for, at which point I need to iterate through each of the reservations, and update the “paid” attribute. So here is the database construction…
1) Class has_many Reservations
2) Reservation belongs_to class, and has_one line_item (and has a decimal :paid attribute)
3) Line_item belongs_to reservation, and belongs_to cart
4) Cart has_many line_items, and has_one order
So, when the student creates the order, a credit card transaction is performed, and if successful, I want to generate and email a receipt to the student.
Originally, in my OrdersController#create method looked something like this…
def create
@cart = current_cart #retrieves the cart
@order = @cart.build_order(params[:order])
# snip saving order and purchase transaction
# I would then iterate over the line-items with
@cart.line_items.each { |li| li.reservation.update_attribute(:paid, li.reservation.fee)}
At this point, however I discovered that the value of the paid attribute would depend on how the line_item was accessed. If I accessed via…
@cart.line_items.each {|li| li.reservation.paid }
the values would be as set. HOWEVER, If it used…
@order.cart.line_items.each {|li| li.reservation.paid }
The value would be zero. (But it was accessing the proper reservation.id)
Then I discovered that if I changed the line that set the paid attribute to the following…
@order.cart.line_items.each { |li| li.reservation.update_attribute(:paid, li.reservation.fee)}
That either of the accesses above would return the correct value.
So I guess my question is, what is the difference in setting the :paid attribute with
@cart.line_items.each ...
and
@order.cart.line_times.each ...
Based on the comment below, I’ve moved the iteration into the Cart model. it now has
def paid_deposit!
self.line_items.includes(:reservation).each do |li|
li.reservation.update_attribute(:paid, li.reservation.deposit)
end
end
However, the same issue exists
The controller has the following code…
@cart.paid_deposit!
@cart.line_items.each {|li| logger.debug "Cart: Res: #{li.reservation.id}, Paid: #{li.reservation.paid}" }
@order.cart.line_items.each {|li| logger.debug "Order: Res: #{li.reservation.id}, Paid: #{li.reservation.paid}" }
The resulting log is…
Reservation Load (0.3ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`id` IN (346, 347, 348)
(0.2ms) BEGIN
(0.2ms) UPDATE `reservations` SET `paid` = 40.0, `updated_at` = '2012-02-05 22:12:36' WHERE `reservations`.`id` = 346
(0.4ms) COMMIT
(0.1ms) BEGIN
(0.1ms) UPDATE `reservations` SET `paid` = 40.0, `updated_at` = '2012-02-05 22:12:36' WHERE `reservations`.`id` = 347
(0.6ms) COMMIT
(0.1ms) BEGIN
(0.1ms) UPDATE `reservations` SET `paid` = 25.0, `updated_at` = '2012-02-05 22:12:36' WHERE `reservations`.`id` = 348
(0.3ms) COMMIT
LineItem Load (0.1ms) SELECT `line_items`.* FROM `line_items` WHERE `line_items`.`cart_id` = 24
Reservation Load (0.2ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`id` = 346 LIMIT 1
Cart: Res: 346, Paid: 40.0
Reservation Load (0.1ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`id` = 347 LIMIT 1
Cart: Res: 347, Paid: 40.0
Reservation Load (0.1ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`id` = 348 LIMIT 1
Cart: Res: 348, Paid: 25.0
Order: Res: 346, Paid: 0.0
Order: Res: 347, Paid: 0.0
Order: Res: 348, Paid: 0.0
As can be seen, referencing the paid amount from the cart gets the proper values, doing the same from the Order does not. Apparently using cached values? Its not accessing the database.
This is kind of an awful way to go about updating things like this. I hope you have a good unit or functional test to demonstrate this broken behaviour so you can be sure it’s fixed when you get it right.
You should probably make a model method to help facilitate this sort of thing. Doing a lot of heavy lifting in your controller makes it hard to test as you can’t easily access the controller environment from the interactive console.
What I can’t figure out is how calling an accessor method like
paiddoes anything useful in youreachloop. That should return a number, not set anything. Theupdate_attributeversion should work because it’s actually changing data.What you could do is push this into the model and make a method that does something like this:
If you can do this all in-database, this might be faster:
Usually I look for ways to side-step the model if just twiddling fields that aren’t subject to validation checks.