Carlos Paramio
2006-Jun-07 11:40 UTC
[Rails] Problem with a setter that converts euros to cents
Hi, I have some problems with an application where I''m using custom accessors to do currency conversions. In my model, I have a price attribute in the database that stores the value in cents, to avoid future problems with float arithmetic and round. But at the views, I would like to show the price in euros, with decimal for the cents. So I defined a new attribute called price_in_euros, and the corresponding accesors: class Report < ActiveRecord::Base validates_numericality_of :price, :only_integer => true attr_accessor :price_in_euros def price_in_euros if self.price self.price / 100.0 else # default price is 1 euro 1 end end def price_in_euros=(euros) self.price = (euros * 100).to_i end end If I test this model with the rails console, all is working fine: $ script/console Loading development environment.>> r = Report.new=> #<Report:0xb74a53c0 @new_record=true, @attributes={"precio"=>nil}>>> r.price_in_euros = 2.45=> 2.45>> r.save=> true>> r=> #<Report:0xb74a53c0 @new_record_before_save=true, @new_record=false, @errors=#<ActiveRecord::Errors:0xb7813264 @errors={}, @base=#<Report:0xb74a53c0 ...>>, @attributes={"id"=>20, "price"=>245}> The attribute "price" has been set to 245 cents. Ok, let''s check the database: mysql> select id, price from reports; +----+-------+ | id | price | +----+-------+ | 20 | 245 | +----+-------+ 1 row in set (0.00 sec) It seems that all is ok. Well, let''s prepare a controller with a couple of actions, insert and edit: class ReportController < ApplicationController def insert @report = Report.new(params[:report]) if @request.post? and @report.save redirect_to :action => ''list'' end end def edit @report = Report.find(params[:id]) @report.attributes = params[:report] if @request.post? and @report.save redirect_to :action => ''list'' end end end Here is part of the view for the insert action: <% form_for :report, :url => { :action => ''insert'' } do |form| %> Price: <%= form.text_field :price_in_euros -%> <%= submit_tag ''Insert'' -%> Here is part of the view for the edit action: <% form_for :report, :url => { :action => ''edit'' } do |form| %> Price: <%= form.text_field :price_in_euros -%> <%= submit_tag ''Save'' -%> Ok. Now I use that views to insert a couple of new reports, giving a value of 2.45 for the first one and 1 for the second one. Here is what it appears at the database: mysql> select id,price from reports; +----+------------+ | id | price | +----+------------+ | 24 | 2 | | 25 | 2147483647 | +----+------------+ 2 rows in set (0.00 sec) Ouch! That''s really strange. And if I try to edit them, I can see a value of 0.02 on the price field for the first one, and 21474836.47 for the second one (the price at the database divided by 100), so the getter accesor seems to be working well, but not the setter. If I click ''Save'' on the edit form, I then get: mysql> select id,price from reports; +----+----------+ | id | price | +----+----------+ | 24 | 0 | | 25 | 21474836 | +----+----------+ 2 rows in set (0.00 sec) What in the hell I''m doing bad? Thanks in advance. -- Carlos Alberto Paramio Danta .--. http://www.sinfoniadebits.com/ |o_o | email : carlosparamio @ gmail.com |:_/ | jabber: parax @ jaim.at // \ \ ----------------------------------------( | | )-- Fingerprint 41C6 D2BE 7DE7 AB61 C23F /''\_ _/`\ F697 5A1D 1849 01B8 D318 \___)=(___/
I think you may find that the problem is that you are getting a string from the html form. Hence it works in irb when you pass a float, but coming from the web form it''s unreliable. There are a couple of things that you could do. Probably one of the more robust is to convert the incoming parameter to a float inside the method. eg def price_in_euros=(ammt) self.price = (ammt.to_f * 100 ).to_i end There are issues with this tho.. If the user enters some dodgy input eg. "abcd" the method will set your price to zero, or if they don''t enter anything there is a problem as well. One solution to this is to put in a callback hook to the before_save callback that checks this value and sets it to a default if it''s zero There are shortcommings with this as well tho particularly with validations They will spit back errors if set for price > 0 def before_save self.price = 100 if (self.price.nil? or self.price < 1) end This way you can use an int or a string for the operation. and you know that if the value is not specified then one euro (100 in the price field) is set in the database. Also since you are specifying these getter and setter methods the call to attr_accessor :price_in_euros is redundant since your over-writing the methods that this sets up. On 6/7/06, Carlos Paramio <carlosparamio@gmail.com> wrote:> > Hi, > > I have some problems with an application where I''m using custom > accessors to do currency conversions. In my model, I have a price > attribute in the database that stores the value in cents, to avoid > future problems with float arithmetic and round. But at the views, I > would like to show the price in euros, with decimal for the cents. So > I defined a new attribute called price_in_euros, and the corresponding > accesors: > > class Report < ActiveRecord::Base > validates_numericality_of :price, :only_integer => true > > attr_accessor :price_in_euros > > def price_in_euros > if self.price > self.price / 100.0 > else > # default price is 1 euro > 1 > end > end > > def price_in_euros=(euros) > self.price = (euros * 100).to_i > end > end > > > If I test this model with the rails console, all is working fine: > > $ script/console > Loading development environment. > >> r = Report.new > => #<Report:0xb74a53c0 @new_record=true, @attributes={"precio"=>nil}> > >> r.price_in_euros = 2.45 > => 2.45 > >> r.save > => true > >> r > => #<Report:0xb74a53c0 @new_record_before_save=true, > @new_record=false, @errors=#<ActiveRecord::Errors:0xb7813264 > @errors={}, @base=#<Report:0xb74a53c0 ...>>, @attributes={"id"=>20, > "price"=>245}> > > > The attribute "price" has been set to 245 cents. Ok, let''s check the > database: > > mysql> select id, price from reports; > +----+-------+ > | id | price | > +----+-------+ > | 20 | 245 | > +----+-------+ > 1 row in set (0.00 sec) > > > It seems that all is ok. Well, let''s prepare a controller with a > couple of actions, insert and edit: > > class ReportController < ApplicationController > def insert > @report = Report.new(params[:report]) > if @request.post? and @report.save > redirect_to :action => ''list'' > end > end > > def edit > @report = Report.find(params[:id]) > @report.attributes = params[:report] > if @request.post? and @report.save > redirect_to :action => ''list'' > end > end > end > > > Here is part of the view for the insert action: > > <% form_for :report, :url => { :action => ''insert'' } do |form| %> > Price: <%= form.text_field :price_in_euros -%> > <%= submit_tag ''Insert'' -%> > > > Here is part of the view for the edit action: > > <% form_for :report, :url => { :action => ''edit'' } do |form| %> > Price: <%= form.text_field :price_in_euros -%> > <%= submit_tag ''Save'' -%> > > > Ok. Now I use that views to insert a couple of new reports, giving a > value of 2.45 for the first one and 1 for the second one. Here is what > it appears at the database: > > mysql> select id,price from reports; > +----+------------+ > | id | price | > +----+------------+ > | 24 | 2 | > | 25 | 2147483647 | > +----+------------+ > 2 rows in set (0.00 sec) > > > Ouch! That''s really strange. > > And if I try to edit them, I can see a value of 0.02 on the price > field for the first one, and 21474836.47 for the second one (the price > at the database divided by 100), so the getter accesor seems to be > working well, but not the setter. If I click ''Save'' on the edit form, > I then get: > > mysql> select id,price from reports; > +----+----------+ > | id | price | > +----+----------+ > | 24 | 0 | > | 25 | 21474836 | > +----+----------+ > 2 rows in set (0.00 sec) > > What in the hell I''m doing bad? Thanks in advance. > -- > Carlos Alberto Paramio Danta .--. > http://www.sinfoniadebits.com/ |o_o | > email : carlosparamio @ gmail.com |:_/ | > jabber: parax @ jaim.at // \ \ > ----------------------------------------( | | )-- > Fingerprint 41C6 D2BE 7DE7 AB61 C23F /''\_ _/`\ > F697 5A1D 1849 01B8 D318 \___)=(___/ > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-------------- next part -------------- An HTML attachment was scrubbed... URL: http://wrath.rubyonrails.org/pipermail/rails/attachments/20060607/1f6bde32/attachment.html
Carlos Paramio
2006-Jun-08 06:32 UTC
[Rails] Problem with a setter that converts euros to cents
Hi Daniel,> I think you may find that the problem is that you are getting a string from > the html form. Hence it works in irb when you pass a float, but coming from > the web form it''s unreliable.Yes! That was. Now works really well.> One solution to this is to put in a callback hook to the before_save > callback that checks this value and sets it to a default if it''s zero There > are shortcommings with this as well tho particularly with validations They > will spit back errors if set for price > 0 > > def before_save > self.price = 100 if (self.price.nil? or self.price < 1) > endGreat, this warranties a sensible value for the price attribute.> Also since you are specifying these getter and setter methods the call to > > attr_accessor :price_in_euros > is redundant since your over-writing the methods that this sets up.I supossed it, but I had some other error message in my view, and I thinked that it was due to the attribute name used in the new way of form definitions: form.text_field :price_in_euros I''ve removed the attr_accessor line and all works like a charm, so finally it was some other thing that gets fixed. Thanks! -- Carlos A. Paramio http://www.sinfoniadebits.com/