Forum: Ruby on Rails Problem with a setter that converts euros to cents

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Carlos P. (Guest)
on 2006-06-07 17:00
(Received via mailing list)
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    \___)=(___/
Daniel -. (Guest)
on 2006-06-07 17:50
(Received via mailing list)
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.
Carlos P. (Guest)
on 2006-06-08 10:33
(Received via mailing list)
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)
> end

Great, 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/
This topic is locked and can not be replied to.