Rails Money not saving properly - ruby-on-rails

I am using the Money gem and composed_of as per this answer: https://stackoverflow.com/a/3843805/4162458
I have an integer column in my database, :discount
product.rb
class Product < ActiveRecord::Base
composed_of :discount_display,
:class_name => 'Money',
:mapping => %w(discount cents),
:converter => Proc.new { |value| Money.new(value) }
And in my form:
<%= f.number_field :discount_display, class: 'form-control', min: "0" %>
However, if I enter 12, it saves in the database as 12 and displays on the form when I refresh as 0.12.
How can I have it so that when you enter "12" it is saved as 1200 in the db and displays properly, as that answers seems to say should happen?

As per documentation,
Represents monetary values as integers, in cents. This avoids floating
point rounding errors.
Hence, if you expect collect dollars from the user, then, you need to convert it to cents before saving in DB. Perhaps, your converter proc should be
Proc.new { |value| Money.new(value * 100) }
or
Proc.new { |value| Money.from_amount(value, "USD") }

Related

"error(s) on assignment of multiparameter attributes" date/time fields

My application has a model "Appointments" which have a start and end attribute both which are datetimes. I am trying to set the date and time parts separately from my form so I can use a separate date and time picker. I thought I should be able to do it like this. From what I ahve read rails should combine the two parts and then parse the combined field as a datetime like it usually would
The error I am getting:
2 error(s) on assignment of multiparameter attributes [error on assignment ["2013-09-16", "15:30"] to start (Missing Parameter - start(3)),error on assignment ["2013-09-16", "16:30"] to end (Missing Parameter - end(3))]
These are the request parameters:
{"utf8"=>"✓", "authenticity_token"=>"OtFaIqpHQFnnphmBmDAcannq5Q9GizwqvvwyJffG6Nk=", "appointment"=>{"patient_id"=>"1", "provider_id"=>"1", "start(1s)"=>"2013-09-16", "start(2s)"=>"15:30", "end(1s)"=>"2013-09-16", "end(2s)"=>"16:30", "status"=>"Confirmed"}, "commit"=>"Create Appointment", "action"=>"create", "controller"=>"appointments"}
My Model
class Appointment < ActiveRecord::Base
belongs_to :patient
belongs_to :practice
belongs_to :provider
validates_associated :patient, :practice, :provider
end
And the relevant part of the view: (its a simple form)
<%= f.input :"start(1s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Date.parse(params[:start]) }%>
<%= f.input :"start(2s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Time.parse(params[:start]).strftime('%R') }%>
<%= f.input :"end(1s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Date.parse(params[:end]) }%>
<%= f.input :"end(2s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Time.parse(params[:end]).strftime('%R') }%>
UPDATE:
THis is now how my model looks like, Ive been trying to do getter/setter methods but I am stuck because start-dat, start_time etc are nil in the model and the parameters aren't sent through
class Appointment < ActiveRecord::Base
belongs_to :patient
belongs_to :practice
belongs_to :provider
validates_associated :patient, :practice, :provider
before_validation :make_start, :make_end
############ Getter Methods for start/end date/time
def start_time
return start.strftime("%X") if start
end
def end_time
return self.end.strftime("%X") if self.end
end
def start_date
return start.strftime("%x") if start
end
def end_date
return self.end.strftime("%x") if self.end
end
def start_time=(time)
end
def end_time=(time)
end
def start_date=(date)
end
def end_date=(date)
end
def make_start
if defined?(start_date)
self.start = DateTime.parse( self.start_date + " " + self.start_time)
end
end
def make_end
if defined?(end_date)
self.start = DateTime.parse( end_date + " " + end_time)
end
end
end
Are you trying to emulate #date_select ? If yes, see second part of answer.
Date database typecast
If you want to assign a DateTime to database, it has to be a DateTime object. Here you use an array of strings, ["2013-09-16", "15:30"].
You can easily compute a datetime from those strings using regexps :
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/ =~ params[ 'start(1s)' ]
/(?<hours>\d+):(?<minutes>\d+)/ =~ params[ 'start(2s)' ]
datetime = DateTime.new( year.to_i, month.to_i, day.to_i, hours.to_i, minutes.to_i )
This will store year, month, day, hours and minutes in local variables and create a new datatime based on it, which you can then assign to your model.
Yet, databases can't store ruby DateTime instances as is, so behind the hood, a conversion is made by rails when saving a date or datetime field to convert it as string. The method used is #to_s(:db), which gives, for example :
DateTime.now.to_s(:db) # => "2013-09-17 09:41:04"
Time.now.to_date.to_s(:db) # => "2013-09-17"
So you could theoretically simply join your strings to have proper date representation, but that wouldn't be a good idea, because :
that's implementation details, nothing say this date format won't change in next rails version
if you try to use the datetime after assigning it and before saving (like, in a before_save), it will be a string and not a datetime
Using active_record datetime helpers
As this would be a pain to do that all the time, rails has helpers to create and use datetime form inputs.
FormBuilder#datetime_select will take only the attribute you want and build all needed inputs :
<%= f.datetime_select :start %>
This will actually create 5 inputs, named respectively "start(1i)" (year), "start(2i)" (month), "start(3i)" (day), "start(4i)" (hours) and "start(5i)" (minutes).
If it feels familiar, it's because it's the exact data we retrieved for building a datetime in first part of this answer. When you assign a hash to a datatime field with those exact keys, it will build a datetime object using their values, like we did in first part.
The problem in your own code is that you've just provided "start(1i)" and "start(2i)". Rails doesn't understand, since you only passed it the year and month, a lot less than what is required to compute a datetime.
See How do ruby on rails multi parameter attributes *really* work (datetime_select)
According to this question, the multiparameter attribute method works for Date but not DateTime objects. In the case of a Date, you would pass year, month and day as separate values, hence the Missing Parameter - start(3), as the expected third parameter is not there.
DateTime, however, requires at least five params for instantiation DateTime.new(2013, 09, 16, 15, 30), so you cannot rely on the automated parsing in your case. You would have to split your params first and in that case, you could easily parse it yourself before saving the object using a before_filter or similar methods.
See the constructor:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/date/rdoc/DateTime.html#method-c-new
and the multiparam description:
http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes

Rails: Virtual Attribute Reader Causing Error During Validations

Rails 3.0.3 application. . .
I'm using a virtual attribute in a model to convert a value stored in the database for display based on a user's preference (U.S. or metric units). I'm doing the conversion in the reader method, but when I test my presence validation I get a NoMethodError because the real attribute is nil. Here's the code:
class Weight < ActiveRecord::Base
belongs_to :user
validates :converted_weight, :numericality => {:greater_than_or_equal_to => 0.1}
before_save :convert_weight
attr_accessor :converted_weight
def converted_weight(attr)
self.weight_entry = attr
end
def converted_weight
unless self.user.nil?
if self.user.miles?
return (self.weight_entry * 2.2).round(1)
else
return self.weight_entry
end
else
return nil
end
end
...
This is the line that's causing the problem:
return (self.weight_entry * 2.2).round(1)
I understand why self.weight_entry is nil, but what's the best way to handle this? Should I just throw in an unless self.weight_entry.nil? check in the reader? Or should I perform this conversion somewhere else? (if yes, where?)
Thanks!
Here's what I've done:
Model
validates :weight_entry, :numericality => {:greater_than_or_equal_to => 0.1}
before_save :convert_weight
attr_reader :converted_weight
def converted_weight
unless self.user.nil?
unless self.weight_entry.nil?
if self.user.miles?
return (self.weight_entry * 2.2).round(1)
else
return self.weight_entry
end
end
else
return nil
end
end
Form
<%= f.label :weight_entry, 'Weight' %><br />
<%= f.text_field :weight_entry, :size => 8, :value => #weight.converted_weight %> <strong><%= weight_units %></strong> (<em>Is this not right? Go to your <%= link_to 'profile', edit_user_registration_path %> to change it</em>)
The unless.self.weight_entry.nil? check allows the validation to do it's job. If anyone knows of a better way to do this I'm open to suggestion.
Thanks!
P.S. The before_save convert_weight method converts U.S. units to metric. I want to store values in the same units consistently so if a user changes her preference later previously stored values don't become invalid.

Ruby Regex for price

This one fails when a zero is at the end
12.12 passes
5.51 passes
12.50 fails
12.60 fails
price_regex = /^\d+(\.\d{2})?$/
why? and how do I fix it?
Some more info
in _form.html.erb
<p>
<%= f.label :price %><br />
<%= f.text_field :price %>
</p>
in menu_item.rb
price_regex = /^\d+(\.\d{2})?$/
validates :price, :presence => true,
:format => { :with => price_regex }
in menu_items_controller.rb
def create
#menu_item = MenuItem.new(params[:menu_item])
if #menu_item.save
respond_with #menu_item, :location => menu_items_url
else
flash[:notice] = "Not Saved"
end
end
price is a decimal in the database with a precision of 2.
You say that price is "a decimal in the database with a precision of 2". That means that price is being represented as a BigDecimal in Ruby and the regex test will be done on the string form of that BigDecimal. A little bit of experimentation will clarify things:
> p = BigDecimal.new('12.50')
=> #<BigDecimal:12a579e98,'0.125E2',18(18)>
> p.to_s
=> "12.5"
And so your regex will fail. You shouldn't be using a regex for this at all, regexes are meant for strings but you're checking a number. You should be able to keep using your regex if you allow for the conversion:
/^\d+(\.\d{1,2})?$/
I'm using Rails 3 with the client_side_validations gem, which means I need a Regexp that works both in Ruby and Javascript. I also have a clear delineation between frontend and backend format--The user should never be able to enter "$12.5", but once it hits the server, I don't care about the trailing 0.
My solution was to add a core extension (in my case, for Float, but BigDecimal would probably be more appropriate in most cases):
class Float
def can_convert_to_i_with_no_loss_of_precision
(self % 1).zero?
end
alias_method :to_s_with_loss_of_trailing_zeroes, :to_s
def to_s
if can_convert_to_i_with_no_loss_of_precision
to_i.to_s
else
"#{to_s_with_loss_of_trailing_zeroes}#{0 if (self * 10 % 1).zero?}"
end
end
end
Now I can use this in a Model, and it plays nicely on the front end (Javascript doesn't convert it to a Float, so the user will always be forced to enter 2 digits after the decimal) and on the backend (where ActiveModel's FormatValidator will call to_s and the core extension will know when to add the trailing 0):
validates :price, :format => { :with => /^\d+(\.\d{2})?$/, :allow_blank => true }
The regex looks fine to me. I tested it at Rubular with the inputs you mentioned and a few more, and it captures all of them correctly.
The problem is likely with some other part of the code. Maybe you are using price = <regex>, whereas you should be using price =~ <regex> to match a string with a regex.

Ruby on Rails - Multiparameter assignment and attr_accessor and 12_hour_time

Ok, I'm slowly getting a grasp on this, but I need some more help.
I'm using a time_select in my view, therefore I'm dealing with a multiparameter assignment. Check.
<%= pt.time_select :time, :twelve_hour => true, :minute_step => 5 %>
BUT I'm doing the naughty thing and using it with an attribute that isn't in the database:
attr_accessor time
Therefore since it can't look at the db, it can't piece together what the multiparameter assignment is supposed to be and therefore I get the following error:
1 error(s) on assignment of multiparameter attributes
Thus I am using information I found here:
composed_of :time,
:class_name => 'DateTime',
:mapping => [%w(DateTime to_s)],
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
Other helpful links: rubyonrails.org | apidock.com
This remove the error, but now the issue is that the plugin I'm using doesn't function properly, I think. I am using http://code.google.com/p/rails-twelve-hour-time-plugin/. The goal being, I'm trying to get the time select to have 3 drop downs: hh:mm am/pm.
So the question is: how do I need to adjust my composed_of method in order for it to be properly converted by the plugin? OR is there a better method to this madness?
I'm not sure what mappers / constructors / converters I need. Right now the object keeps the hour and minute, except the hour isn't converted to 24 hour (which the plugin is supposed to take care of, I thought).
The issue was that I didn't follow the article properly. I should have been using the Time model.
composed_of :time,
:class_name => 'Time',
:mapping => [%w(Time to_s)],
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
Looks like this is a common problem. For more information about it check out this link. The suggestion given there is to do:
class Whatever < ActiveRecord::Base
...
attr_accessor :arrival_time
columns_hash["arrival_time"] = ActiveRecord::ConnectionAdapters::Column.new("arrival_time", nil, "time")
end
I'm still working out bugs, but I'll update this when I get something working.

Rails 3 - Seed.rb data for Money Class

I am trying out seeds.rb for the first time, and one of my data models uses encapsulation provided by the money gem.
Relevant gems:
money (3.6.1)
rails (3.0.5)
My model thus far:
app/models/list.rb
class List < ActiveRecord::Base
attr_accessible :alias, :unit, :participating_manufacturer, :quantity
:latest_price_cents, :latest_price_currency, :url
belongs_to :user
composed_of :latest_price,
:class_name => "Money",
:mapping => [%w(latest_price_cents latest_price_cents), %w(latest_price_currency currency_as_string)],
:constructor => Proc.new {
|latest_price_cents, latest_price_currency| Money.new(latest_price_cents ||
0, latest_price_currency || Money.default_currency)
},
:converter => Proc.new {
|value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError,
"Can't convert #{value.class} to Money")
}
end
1) (Addressed successfully)
2) When I get to writing validations, would it be best to write them for the :latest_price attribute or for the :latest_price_cents & :latest_price_currency attributes seperately?
/db/seeds.rb
users = User.create([{ :name => "Foo", :email => "foo#gmail.com",
:password => "foobar", :password_confirmation => "foobar" }])
# etc, will add more users to the array
list = List.create(:user_id => users.first.id, :alias => "Januvia 100mg",
:unit => "tablet", :participating_manufacturer => "Merck",
:quantity => 30, :latest_price_cents => 7500,
:latest_price_currency => "USD", :url =>
"http://www.foobar.com/januvia/100mg-tablets/")
3) Perhaps it is minutiae, but in the seed, should I be assigning values to the virtual :latest_price attribute or to the latest_price_cents and latest_price_currency attributes directly? Is there any way to use faker rather than /db/seeds.rb to perform this task?
I am new to rails and web development.
I can't see your latest_price attribute anywhere, so I'm not sure how to answer your question. Generally, you should validate the attributes entered in the user form. So if a user enters latest_price_cents and latest_price_currency in a form, then they're the ones which need validating.
There's a bug in your seed file. You want to pass in a hash, not an array, when creating a new user; and users should be an array.
users = []
users << User.create!(:name => "Foo",
:email => "foo#gmail.com",
:password => "foobar",)
:password_confirmation => "foobar")
However, if you're considering faker because you want to create some dummy data, take a look at Machinist or Factory Girl. They're designed for creating dummy data, normally for automated tests.
Once you've set up some blueprints, if you want to create dummy data in your seeds file, you can do something like this in seeds.rb:
20.times { List.make } unless Rails.env.production?

Resources