Carlos Paramio
2006-Jun-07 14:14 UTC
[Rails] Setter that converts a float attribute to integer
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 Paramio http://www.sinfoniadebits.com/
Chris Hall
2006-Jun-08 12:36 UTC
[Rails] Setter that converts a float attribute to integer
to see why this is happening you have to know that the data you are submitting from the form is text, so Ruby/Rails is going to see it as a string... irb example irb(main):008:0> ("2.45"*100).to_i => 2 irb(main):009:0> ("1"*100).to_i => 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 now, if we convert the strings to floats first irb(main):011:0> ("2.45".to_f*100.0).to_i => 245 irb(main):012:0> ("1".to_f*100.0).to_i => 100 then you will see the data as you expect it to be. your tests worked because you used floats/ints, not strings. so change your method to: def price_in_euros=(euros) self.price = (euros.to_f * 100.0).to_i end and give it another go. 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 Paramio > http://www.sinfoniadebits.com/ > > _______________________________________________ > 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/20060608/11d63350/attachment.html