Custom formatting of float within form_for - ruby-on-rails

Is there any way to control the float format in a form field?
I want to format a float like an integer if the modulus is 0, otherwise display the float as is. I overrode the Model accessor to do this formatting.
When an edit form is loaded, I would like to have the following transformations:
stored value | accessor returns | form field shows
---------------------------------------------------
1.0 | 1 | 1
1.5 | 1.5 | 1.5
However, form_for appears to be accessing the attribute directly, thereby displaying the float as is.
Any ideas on how to get around this? Thanks.

Using
def my_float
raw = read_attribute(:my_float)
if raw == raw.to_i
raw.to_i
else
raw
end
end
within form_for will not work as noted before. Tried multiple times. IMHO it is one of more severe design issues with rails. In general you don't have straight forward (restful) access to model from your view.

You could override the attribute reader, something like this:
def myfloat
if #myfloat == #myfloat.to_i
#myfloat.to_i
else
#myfloat
end
end
Now the returned value are correctly formatted for your form and still usable in your application.

I do believe it will work when you do something like this:
<%= f.text_field :field_attribute, :value => format_method(f.object.field_attribute) %>
format_method is whatever method you use within the model to override the formatting when accessing it that way.

Veger's solution will work if you use read_attribute to get the "raw" value:
def myfloat
raw = read_attribute(:myfloat)
if raw == raw.to_i
raw.to_i
else
raw
end
end
As always when comparing floats to integers, you'd want to be careful about rounding.

You can override respond_to? in the Model to stop value_before_type_cast from being called.
def respond_to?(*args)
if args.first.to_s == "my_float_before_type_cast"
false
else
super
end
end
And then you need also:
def my_float
raw = read_attribute(:my_float)
if raw == raw.to_i
raw.to_i
else
raw
end
end

Related

Rails convert integer value before save

I have an database with an enginze size for the car
Therefore user can write something like 2.5 (like in liters) or 2500 (cc)
Later on I have an sorting and it should using 999-9999 values to compare
I came up the function below, but I would like it be more flexible. Moreover, 2.5 causing the result of 2000 now because looks like Rails converting value before triggering before_save
How do I make convert right and detect if there is an point or comma in input?
before_save :covert_engine
private
def covert_engine
if self.car_engine_size.present?
if Math.log10(self.car_engine_size).to_i + 1 < 4
self.car_engine_size = self.car_engine_size * 1000
end
end
end
P.S. self.car_engine_size is an integer in database
If you want the user to be able to use different units of input I would make it explicit to the user by letting them select the unit.
Start by creating a virtual attribute
class Car
attr_accessor :engine_size_unit
end
Add the attribute to the form and whitelist it in the controller
<%= f.number_field :engine_size %>
<%= f.select :engine_size_unit, ['cc', 'l']) %>
Then convert the value in the model using the value before the typecast
class Car
before_save :covert_engine,
if: -> { car_engine_size.present? && engine_size_unit == 'l' }
def covert_engine
converted = car_engine_size_before_type_cast * 1000
self[:car_engine_size] = converted.to_i
end
end

save multiple parameters in a loop, rails controller

I've created a form with about 40 fields available to edit, I'm trying to save them to a database using the controller. I currently have this code:
c = Form.find(params[:id])
if c
params.each do |k,v|
c.k = params[:v]
end
Which doesn't work, I get this error: undefined method 'k='
if I was going to write them all out manually it would look like this:
c = Form.find(params[:id])
if c
c.title = params[:title]
c.reference = params[:reference]
....
etc.
Assuming that you're trying to update the attributes on your Form record based on what gets passed into params, try this as a basic outline:
c = Form.find_by_id(params[:id])
if c
params.each do |k, v|
c[k] = v
end
c.save!
end
Your original code's use of params[:v] was probably not doing what you were intending, and you really meant for it to be params[:k] instead. However there's actually no need to look up the value for that key inside the loop like that because you already have the value at hand in v.
Here's a quick rundown on the ways of interacting with ActiveRecord attributes: http://www.davidverhasselt.com/2011/06/28/5-ways-to-set-attributes-in-activerecord/
i dont know what you are trying todo but your code seems to be very odd. Solution is as follow
c.send "#{k}=", params[:v]
What about
c = Form.find(params[:id])
c.update_attributes(params[:form])
Note that I guessed the [:form] part in the second line, it depends on your form. check your html source, and see if your fields are something like this:
<input name="form[field_name]" ...
As you see, name contains an "array like" form. Check your HTML source and adapt (so if its name="foo[field_name]", you need to use c.update_attributes(params[:foo]))

Multiplication in Ruby on Rails Model of nil values

I have several calculated values as part of my risk.rb model
before_save :calculate_risk
def calculate_risk
self.risk1_total = self.component1 * self.component2 * self.component3
self.risk2_total = self.component4 * self.component5 * self.component6
...
end
I want to be able to create a risk without filling out the form completely thus each of those components would be nil. So this method creates an error because * is not a valid method for a nil. What is the best way to handle this? I have considered
def calculate_risk
if self.component1.nil? || self.component2.nil? || self.component3.nil?
self.risk1_total = self.component1 * self.component 2 * self.component3
elsif ...
end
However, this is obviously inefficient and repetitive. I also considered initializing all of these values, though I do not know the most efficient way of doing this.
You can do something like the following:
before_save :calculate_risk
def calculate_risk
self.risk1_total = [self.component1,self.component2,self.component3].compact.inject(:*)
self.risk2_total = [self.component4,self.component5,self.component6].compact.inject(:*)
...
end
This is assuming you want nil values to just be dropped from the calculation. This will give a result of nil if all values are nil. You could replace the nils with zeroes if you prefer. You may also be interested in the :reject method or other cools tools in the Ruby Array and Enumerable classes.
I hope that helps.

Where is the Rails method that converts data from `datetime_select` into a DateTime object?

When I use <%= f.datetime_select :somedate %> in a form, it generates HTML like:
<select id="some_date_1i" name="somedate1(1i)"> #year
<select id="some_date_2i" name="somedate1(2i)"> #month
<select id="some_date_3i" name="somedate1(3i)"> #day
<select id="some_date_4i" name="somedate1(4i)"> #hour
<select id="some_date_5i" name="somedate1(5i)"> #minute
When that form is submitted, the somedate1(<n>i) values are received:
{"date1(1i)"=>"2011", "date1(2i)"=>"2", "date1(3i)"=>"21", "date1(4i)"=>"19", "date1(5i)"=>"25"}
How can I convert that into a DateTime object?
I could write my own method to do this, but since Rails already is able to do the conversion, I was wondering if I could call that Rails method to do it for me?
I don't know where to look for that method.
I'm ultimately trying to solve "How to handle date/times in POST parameters?" and this question is the first step in trying to find a solution to that other problem.
This conversion happens within ActiveRecord when you save your model.
You could work around it with something like this:
somedate = DateTime.new(params["date1(1i)"].to_i,
params["date1(2i)"].to_i,
params["date1(3i)"].to_i,
params["date1(4i)"].to_i,
params["date1(5i)"].to_i)
DateTime::new is an alias of DateTime::civil (ruby-doc)
The start of that code path, seems to be right about here:
https://github.com/rails/rails/blob/d90b4e2/activerecord/lib/active_record/base.rb#L1811
That was tricky to find! I hope this helps you find what you need
Hi I have added the following on the ApplicationController, and it does this conversion.
#extract a datetime object from params, useful for receiving datetime_select attributes
#out of any activemodel
def parse_datetime_params params, label, utc_or_local = :local
begin
year = params[(label.to_s + '(1i)').to_sym].to_i
month = params[(label.to_s + '(2i)').to_sym].to_i
mday = params[(label.to_s + '(3i)').to_sym].to_i
hour = (params[(label.to_s + '(4i)').to_sym] || 0).to_i
minute = (params[(label.to_s + '(5i)').to_sym] || 0).to_i
second = (params[(label.to_s + '(6i)').to_sym] || 0).to_i
return DateTime.civil_from_format(utc_or_local,year,month,mday,hour,minute,second)
rescue => e
return nil
end
end
Had to do something very similar, and ended up using this method:
def time_value(hash, field)
Time.zone.local(*(1..5).map { |i| hash["#{field}(#{i}i)"] })
end
time = time_value(params, 'start_time')
See also: TimeZone.local
Someone else here offered solution of using DateTime.new, but that won't work in Postgresql. That will save the record as is, that is, as it was inserted in form, and thus it won't save in database as utc time, if you are using "timestamp without timezone". I spent hours trying to figure out this one, and the solution was to use Time.new rather than DateTime.new:
datetime = Time.new(params["fuel_date(1i)"].to_i, params["fuel_date(2i)"].to_i,
params["fuel_date(3i)"].to_i, params["fuel_date(4i)"].to_i,
params["fuel_date(5i)"].to_i)
I had this issue with Rails 4. It turned out I forgot to add the permitted params to my controller:
def event_params
params.require(:event).permit(....., :start_time, :end_time,...)
end
This is the method I use - it returns the deleted keys as a new hash.
class Hash
def delete_by_keys(*keys)
keys.each_with_object({}) { |k, h| h[k] = delete(k) if include? k }
end
end

Rails, using time_select on a non active record model

I am trying to use a time_select to input a time into a model that will then perform some calculations.
the time_select helper prepares the params that is return so that it can be used in a multi-parameter assignment to an Active Record object.
Something like the following
Parameters: {"commit"=>"Calculate", "authenticity_token"=>"eQ/wixLHfrboPd/Ol5IkhQ4lENpt9vc4j0PcIw0Iy/M=", "calculator"=>{"time(2i)"=>"6", "time(3i)"=>"10", "time(4i)"=>"17", "time(5i)"=>"15", "time(1i)"=>"2009"}}
My question is, what is the best way to use this format in a non-active record model. Also on a side note. What is the meaning of the (5i), (4i) etc.? (Other than the obvious reason to distinguish the different time values, basically why it was named this way)
Thank you
You can create a method in the non active record model as follows
# This will return a Time object from provided hash
def parse_calculator_time(hash)
Time.parse("#{hash['time1i']}-#{hash['time2i']}-#{hash['time3i']} #{hash['time4i']}:#{hash['time5i']}")
end
You can then call the method from the controller action as follows
time_object = YourModel.parse_calculator_time(params[:calculator])
It may not be the best solution, but it is simple to use.
Cheers :)
The letter after the number stands for the type to which you wish it to be cast. In this case, integer. It could also be f for float or s for string.
I just did this myself and the easiest way that I could find was to basically copy/paste the Rails code into my base module (or abstract object).
I copied the following functions verbatim from ActiveRecord::Base
assign_multiparameter_attributes(pairs)
extract_callstack_for_multiparameter_attributes(pairs)
type_cast_attribute_value(multiparameter_name, value)
find_parameter_position(multiparameter_name)
I also have the following methods which call/use them:
def setup_parameters(params = {})
new_params = {}
multi_parameter_attributes = []
params.each do |k,v|
if k.to_s.include?("(")
multi_parameter_attributes << [ k.to_s, v ]
else
new_params[k.to_s] = v
end
end
new_params.merge(assign_multiparameter_attributes(multi_parameter_attributes))
end
# Very simplified version of the ActiveRecord::Base method that handles only dates/times
def execute_callstack_for_multiparameter_attributes(callstack)
attributes = {}
callstack.each do |name, values|
if values.empty?
send(name + '=', nil)
else
value = case values.size
when 2 then t = Time.new; Time.local(t.year, t.month, t.day, values[0], values[min], 0, 0)
when 5 then t = Time.time_with_datetime_fallback(:local, *values)
when 3 then Date.new(*values)
else nil
end
attributes[name.to_s] = value
end
end
attributes
end
If you find a better solution, please let me know :-)

Resources