I have an order form with selectors for a product and boolean sliders for add-on options. I want to calculate the total price before saving to the db. I am using a before_save :set_total action in the model, but I can't change the value of the price field. Are model values read only? If so, where should I put the model object 'set_total' to affect the price value? I don't think it is wise to use an after-save :set_total with an update olineorders set price = #{tot price};
my model file extract:
class Olineorder < ApplicationRecord
before_save :set_total
(other stuff in here)
private
def set_total
puts "\n\ndef set_total"
puts "\tprice: #{price.inspect}\t#{Olineorder.inspect}\t\n"
@a = Price.find_by_sql("select pr.product_id, pr.price as theprice, p.name from prices as pr, products as p where pr.product_id == p.id;")
@b = Hash.new
@a.each do |f|
@b[f.name] = f.theprice.to_f
# puts f.name, f.theprice
end
totprice = @b["#{selection}"]
totprice += @b["Certificate"] if cert == true
totprice += @b["Biographical Skectch"] if bio == true
totprice += @b["Photograph"] if pic == true
self.price = printf('$%.2f',totprice)
==>Note: I al so tried: price = .... with same result. <===
puts "\n\tprice: #{price.inspect} -- total price -- #{totprice}\t#{Olineorder.inspect}\t\n"
puts "\ndef_set_toal ---<<< END\n"
end
Below is the logging output in the development.log
------------------ Output in log file ---------------
Started POST "/olineorders" for ::1 at 2024-10-14 18:04:10 -0700
Processing by OlineordersController#create as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "olineorder"=>{--REDACTED---, "selection"=>"Worker's Brick", "cert"=>"true", "bio"=>"true", "pic"=>"true", "brick1"=>"", "brick2"=>"", "brick3"=>"", "price"=>"0.00", "comments"=>"", "block"=>"", "log"=>"0", "status"=>""}, "commit"=>"Create Olineorder"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = ? ORDER BY "users"."id" ASC LIMIT ? [["remember_token", "[FILTERED]"], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:14:in `ck_for_accs_level'
Product Load (0.1ms) select id, name from products;
↳ app/controllers/olineorders_controller.rb:113:in `set_product_list'
def set_total
price: "0.00" Olineorder(id: integer, last_name: string, first_name: string, address1: string, address2: string, city: string, state: string, zip: string, phone: string, email: string, selection: string, cert: boolean, bio: boolean, pic: boolean, comments: string, block: string, log: string, brick1: string, brick2: string, brick3: string, status: string, price: string, created_at: datetime, updated_at: datetime)
TRANSACTION (0.0ms) begin transaction
↳ app/models/olineorder.rb:35:in `set_total'
Price Load (0.4ms) select pr.product_id, pr.price as theprice, p.name from prices as pr, products as p where pr.product_id == p.id;
↳ app/models/olineorder.rb:35:in `set_total'
$135.00
price: nil -- total price -- 135.0 Olineorder(id: integer--redacted---, price: string, created_at: datetime, updated_at: datetime)
def_set_toal ---<<< END
Olineorder Create (0.1ms) INSERT INTO "olineorders" (---redacted---- "price", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [---REDACTED---, ["price", "0.00"], ["created_at", "2024-10-15 01:04:11.142454"], ["updated_at", "2024-10-15 01:04:11.142454"]]
↳ app/controllers/olineorders_controller.rb:41:in `block in create'
TRANSACTION (0.6ms) commit transaction
↳ app/controllers/olineorders_controller.rb:41:in `block in create'
Redirected to http://localhost:3000/olineorders/17
Completed 302 Found in 41ms (ActiveRecord: 2.8ms (4 queries, 0 cached) | GC: 1.0ms)
Started GET "/olineorders/17" for ::1 at 2024-10-14 18:04:11 -0700
Processing by OlineordersController#show as HTML
Parameters: {"id"=>"17"}
Olineorder Load (0.1ms) SELECT "olineorders".* FROM "olineorders" WHERE "olineorders"."id" = ? LIMIT ? [["id", 17], ["LIMIT", 1]]
↳ app/controllers/olineorders_controller.rb:109:in `set_olineorder'
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = ? ORDER BY "users"."id" ASC LIMIT ? [["remember_token", "[FILTERED]"], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:14:in `ck_for_accs_level'
Product Load (0.0ms) select id, name from products;
↳ app/controllers/olineorders_controller.rb:113:in `set_product_list'
Rendering layout layouts/application.html.erb
Rendering olineorders/show.html.erb within layouts/application
Rendered olineorders/_olineorder.html.erb (Duration: 0.3ms | GC: 0.0ms)
Rendered olineorders/show.html.erb within layouts/application (Duration: 1.7ms | GC: 0.0ms)
Rendered layouts/_pg_links.html.erb (Duration: 0.4ms | GC: 0.0ms)
Rendered layout layouts/application.html.erb (Duration: 3.3ms | GC: 0.1ms)
Completed 200 OK in 9ms (Views: 5.0ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.2ms)
self.price = printf('$%.2f',totprice)
This does not set the price, it prints it. printf
is for printing and returns nil
so that does self.price = nil
. You want sprintf
.
If price
is a number you probably don't want to add a $
to it. Generally avoid adding formatting to numbers in your database, it makes them harder to work with. In that case, use Numeric#round
.
self.price = totprice.round(2)
@a
and @b
are instance variables on the object which will persist. You probably don't want that, and you risk accidentally overwriting other instance variables such as @price
. If you just need a local variable in a method use a
and b
... tho don't use a
and b
, use descriptive names. See Read This If You Want to Understand Instance Variables in Ruby.if cert == true
. if cert
is sufficient.Consider using rubocop and rubocop-rails to guide you.