Rails: Combine 3 select drop-downs to modify 1 attribute of a model - ruby-on-rails

So normally I use the date_select helper in my Rails applications in my Models' forms, but in the most recent application I was building, I needed to specify individual css id's for each drop down (month, day, year), and that isn't possible with date_select. So instead, I've been using select_month, select_day, and select_year helpers. Here's the problem: how do I get them to all describe one datetime record in my database?
(I've looked at this question by the way, but it looks pretty useless to me. Plus, I don't want to do some hacky jQuery stuff handling this.)
Here's what I have so far:
#default_time_for is just a helper method that returns the default time for the specified "period" of time (month,day,etc.)
<%= select_month(default_time_for(:month)) %>
<%= select_day(default_time_for(:day)) %>
<%= select_year(default_time_for(:year), {:start_year => Time.now.year-18, :end_year => 1930}) %>

I'd combine them on the server side:
require 'date'
def update
date = Date.parse(params[:year] + params[:month] + params[:day])
# Add to object and save
end
You can still do mass-assignment, assuming that date is the field you want to assign to, you could do the following:
def update
date = Date.parse(params[:year] + params[:month] + params[:day])
params = params.merge( { :user => { :date => date }})
#user = User.find(params[:id])
if #user.update_attributes(params)
# save was good
else
# save was bad
end
end
Also make sure you're familiar and mitigated against the security issued cased by Mass Assignment if you're going to use the above code.

Related

Formatting credit card number in a number_field_tag [duplicate]

I would like to make editing form fields as user-friendly as possible. For example, for numeric values, I would like the field to be displayed with commas (like number_with_precision).
This is easy enough on the display side, but what about editing? Is there a good way to do this?
I am using the Rails FormBuilder. Upon investigation, I found that it uses InstanceTag, which gets the values for fields by using <attribute>_value_before_type_cast which means overriding <attribute> won't get called.
The best I have come up with so far is something like this:
<%= f.text_field :my_attribute, :value => number_with_precision(f.object.my_attribute) %>
Or my_attribute could return the formatted value, like this:
def my_attribute
ApplicationController.helpers.number_with_precision(read_attribute(:my_attribute))
end
But you still have to use :value
<%= f.text_field :my_attribute, :value => f.object.my_attribute %>
This seems like a lot of work.
I prefer your first answer, with the formatting being done in the view. However, if you want to perform the formatting in the model, you can use wrapper methods for the getter and setter, and avoid having to use the :value option entirely.
You'd end up with something like this.
def my_attribute_string
foo_formatter(myattribute)
end
def my_attribute_string=(s)
# Parse "s" or do whatever you need to with it, then set your real attribute.
end
<%= f.text_field :my_attribute_string %>
Railscasts covered this with a Time object in a text_field in episode #32. The really clever part of this is how they handle validation errors. It's worth watching the episode for that alone.
This is an old question, but in case anyone comes across this you could use the number_to_X helpers. They have all of the attributes you could ever want for displaying your edit value:
<%= f.text_field :my_number, :value => number_to_human(f.object.my_number, :separator => '', :unit => '', :delimiter => '', :precision => 0) %>
There are more attributes available too: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html
If you want a format to be created or maintained during editing, you will need to add Javascript to implement "masks." Here is a demo.
It was the first hit in these results.
You can use the number_format plugin. By specifying a number_format for an existing numeric attribute inside your model, the attribute will now appear as formatted to Rails in all forms and views. It will also be parsed back from that format (when assigned via forms) prior to insertion into the database. (The plugin also creates purely numeric unformatted_<attribute-name> accessors which can continue to be used for arithmetic, or for direct numerical assignment or retrieval by you for seamless integration.)
class MyModel < ActiveRecord::Base
# this model has the balance attribute, which we
# want to display using formatting in views,
# although it is stored as a numeric in the database
number_format :balance,
:precision => 2,
:delimiter => ',',
:strip_trailing_zeros => false
def increment_balance
unformatted_balance += 10
end
You can also combine the above with a Javascript solution, which can force the user to maintain the decimal point and thousands separators in place while editing, although this is really not necessary.
I have done something similar. We format times and lengths using a custom form builder. It makes use of the existing text_field, but wraps it so the value can be customized:
class SuperFormBuilder < ActionView::Helpers::FormBuilder
include ApplicationHelper
include FormHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
def length_field(label,*args)
scale = 'medium'
args.each do |v|
if v.has_key?(:scale)
scale = v[:scale]
v.delete(:scale)
end
end
value = length_conversion(#object.send(label.to_sym),scale)
options = (args.length > 0) ? args.pop : {}
return has_error(label, text_field_tag(field_name(label),value,*args) + ' ' + length_unit(scale))
end
private
def field_name(label)
return #object_name + "[#{label}]"
end
def has_error(label, output)
return "<div class='fieldWithErrors'>#{output}</div>" if #object.errors[label]
return output
end
And it is used like this:
<%= form_for( #section, {:action => 'save', :id => #section.id}, :builder => SuperFormBuilder) do |sf| %>
<%= sf.length_field :feed_size_min_w, :size => 3, :scale => 'small' %>
<% end %>
The end result is a value in the appropriate unit based off their choice on system (Metric, Imperial) and scale IE small = inches or millimeters.
I basically copied the text_field method from the existing form builder, which uses the text_field_tag itself.
There are two gotchas: 1) Knowing the name of the object field and how to access the object to get the value which you want to format. 2) Getting the name right so when the form is submitted it is part of the correct params hash.
The form builder is given a class variable #object. You can get the value of the field using the .send method. In my case I send the label :feed_size_min_w to the #object and get its length back. I then convert it to my desired format, and give it to the text_field_tag.
The name of the field is key to having it end up in the params hash, in my instance the params[:sections] one. I made a little helper function called field_name that takes care of this.
Finally the has_error wraps the field in an error div if there are errors on that label.
I needed "nicer" format on some specified text fields, resolved it by adding this to my initializers. Seems to work nicely on Rails ~= 5.2 and it should be easy to customize.
class ActionView::Helpers::Tags::TextField
private
def value_before_type_cast # override method in ActionView::Helpers::Tags::Base
v = super
# format as you like, when you like
if #options.delete(:nice_decimal)
v = v.to_s.gsub('.', ',') if v.is_a?(BigDecimal)
end
v
end
end
Usage in form f
<%= f.text_field :foo, nice_decimal: true %>

Rails 4 - Convert datetime into separate date and time fields

How can you convert a mysql datetime field into two form fields (1) date only, (2) time only, and combine both fields back into datetime format on form submit?
This would allow the use of the following gems, but store the dates in a single datetime field:
gem 'bootstrap-datepicker-rails'
gem 'bootstrap-timepicker-rails'
Thanks in advance!
Found the solution with help from #Althaf
Added virtual attributes to model.rb
Used before_save callback to convert back to datetime.
before_save :convert_to_datetime
def sched_date_field
sched_date.strftime("%d/%m/%Y") if sched_date.present?
end
def sched_time_field
sched_time.strftime("%I:%M%p") if sched_time.present?
end
def sched_date_field=(date)
# Change back to datetime friendly format
#sched_date_field = Date.parse(date).strftime("%Y-%m-%d")
end
def sched_time_field=(time)
# Change back to datetime friendly format
#sched_time_field = Time.parse(time).strftime("%H:%M:%S")
end
def convert_to_datetime
self.sched_time = DateTime.parse("#{#sched_date_field} #{#sched_time_field}")
end
Using Rails 4, needed to add sched_date_field and sched_time_field to strong params in controller.rb
Here are the fields in _form.html.erb
<%= f.label :sched_date_field, "Scheduled Date" %>
<%= f.text_field :sched_date_field, :class => "datepicker" %>
<%= f.label :sched_time_field, "Scheduled Time" %>
<%= f.text_field :sched_time_field, :class => "timepicker" %>
You can use date_time_attribute gem:
class MyModel < ActiveRecord::Base
include DateTimeAttribute
date_time_attribute :scheduled_at
end
It will allow you to set schedule_at_date and scheduled_at_time separately. Once attributes are set, values will be combined into schedule_at.
You could use virtual attributes See this Railscast and if you have a pro subscription the revised one.
Basically in the view you would the following
<%= f.label :date_field %>
<%= f.text :date_field %>
<%= f.label :time_field %>
<%= f.text :time_field %>
Your database would still keep a field which I'll call full_date
Now in your model you would have to define the above 2 fields as follows.
def date_field # What this returns will be what is shown in the field
full_date.strftime("%m-%d'%y") if full_date.present?
end
def time_field
full_date.strftime("%I:%M%p") if full_date.present?
end
def time_field=(time)
full_date = DateTime.parse("#{date_field} #{time_field})
end
Since it looks like you are using Rails 4, you'll have to permit date_field and time_field in your strong parameters.
Alternatively, I set up a solution in the controller that does all the datetime conversions before the object gets created, because changing the data in the model impacted all my tests and validations. "Event" is the object I'm creating here with the datetime values being assigned to it.
#In the controller:
def convert_to_datetime_and_assign(event, params)
date_field = Date.parse(params[:date_field]).strftime("%Y-%m-%d")
start_time_field = Time.parse(params[:start_time_field]).strftime("%H:%M:%S")
end_time_field = Time.parse(params[:end_time_field]).strftime("%H:%M:%S")
event.start_time = DateTime.parse("#{date_field} #{start_time_field}")
event.end_time = DateTime.parse("#{date_field} #{end_time_field}")
event
rescue ArgumentError
event.errors.add(:start_time, :invalid, message: "Date or time was invalid")
event
end
in the create and update controller methods I called the method above:
#event = convert_to_datetime_and_assign(#event, event_params)
I added fields for date_field, start_time_field and end_time_field in my forms for creating/updating "events". And in the model I added an accessor to be able to access those values.
attr_accessor :date_field, :start_time_field, :end_time_field

populating a custom time field on ruby on rails with simple_form

I'm a beginner to ruby and ruby on rails, and I'm attempting to create a complex form using simple_form. Everything is working already, but I wanted to customize the "time" field in a very specific way.
When I use:
<%= f.input :hour %>
It renders two select fields, being the "hour" field populated with options from 00 to 23, and the "minute" field populated with options from 00 to 59.
But that's not what I want. I want to replace the use of two select fields for the time with a single select field with custom pre-populated options, with time ranging from 8am until 22pm, in 15 minutes increments, and also have the text for the options show times with the syntax "8h00", "8h15", "8h30", "8h45", "9h00", "18h00", "18h15", "18h30", "18h45", etc.. being "21h45" the last option available.
I have managed to customize it a bit more using these options:
<%= f.input :hour, :as => :time, :minute_step => 15 %>
Although that solves the 15 minute increment problem, it still renders two select fields.
This would be the html equivalent of what I wanted to have simple_form render for me:
<select name="hour">
<option value="08:00:00">08h00</option>
<option value="08:15:00">08h15</option>
<option value="08:30:00">08h30</option>
<option value="08:45:00">08h45</option>
...
<option value="21:30:00">21h30</option>
<option value="21:45:00">21h45</option>
</select>
I'm pretty sure this is very easy to implement using a collection that loops from 8..21 and then loops again with '00', '15', '30' and '45', and then outputs options in the syntax that I want (hour + "h" + minute).
I want to create a custom "date" field, because there will many date fields in a single form (for a "appointment" related application that I'm creating), but since I'm a beginner on ruby on rails, I'm really lost on what would be the most smart way to implement this. I'm not sure if I should implement a custom field in simple_form, if I should use a helper function, or what.
With help from Carlos Antonio da Silva, from simpleform's mailing list, we've fixed this by creating a custom input like this:
app/inputs/hour_input.rb
class HourInput < SimpleForm::Inputs::Base
def input
#builder.select(attribute_name, hour_options, { :selected => selected_value }, { :class => "input-medium" })
end
private
# The "Selecione..." string could also be translated with something like: I18n.t("helpers.select.prompt')
def hour_options
hour = [['Selecione...', '00:00:00']]
(8..21).each do |h|
%w(00 15 30 45).each do |m|
hour.push ["#{h}h#{m}", "#{"%02d" % h}:#{m}:00"]
end
end
hour
end
def selected_value
value = object.send(attribute_name)
value && value.strftime("%H:%M:%S")
end
end
and then <%= f.input :hora, :as => :hour %> in the view.
With suggestions from simpleform's google mailing list, this is how I fixed my own problem:
in the view file, I created a input like this:
<%= f.input :hour, collection: options_for_time_select, selected: f.object.hour.strftime("%H:%M:%S") %>
And in application_helper.rb file I created a function this way:
module ApplicationHelper
def options_for_time_select
hour = Array.new
for $h in 8..21 do
for $m in ['00', '15', '30', '45'] do
hour.push [$h.to_s + "h" + $m.to_s, "%02d" % $h + ":" + $m + ":00"]
end
end
hour
end
end
I have no idea if this is the most elegant way to solve this problem, or if it really works on every scenario. I would love to get corrections or a better solution, if possible.

How to display Rails select field values rather than stored integers in other views

I'm using a select field in a Rails app that is NOT tied to a related model, but stores integer values for a static series of options , i.e.,
<%= select (:this_model, :this_field, [['Option1',1],['Option2',2],['Option3',3],['Option4',4]] ) %>
In a show/ index view, if I want to display the option text (i.e. Option1, Option2, etc) rather than the integer value stored in the database, how do I achieve this?
Thanks for helping a noob learn the ropes!
EDIT
Based on Thorsten's suggestion below, I implemented the following. But it is returning nil, and I can't figure out why.
Invoice model:
##payment_status_data = { 1 => "Pending Invoice" , 2 => "Invoiced" , 3 => "Deposit Received", 4 => "Paid in Full"}
def text_for_payment_status
##payment_status_data[payment_status]
end
Invoice show view:
Payment Status: <%= #invoice.text_for_payment_status %>
In the console:
irb > i=Invoice.find(4)
=> [#<Invoice id: 4, payment_status: 1 >]
irb > i.text_for_payment_status
=> nil
I've tried defining the hash with and without quotes around the keys. What am I missing?
something like this would work:
<%= form_for #my_model_object do |form| %>
<%= form.label :column_name "Some Description" %>
<%= form.select :field_that_stores_id, options_for_select({"text1" => "key1", "text 2" => "key2"}) %>
<% end %>
Update
If you later want to display the text you can get it from a simple hash like this:
{"key1" => "text 1", "key2" => "text2"}[#my_object.field_that_stores_id]
But you better store this hash somewhere in a central place like the model.
class MyModel < ActiveRecord
##my_select_something_data = {"key1" => "text 1", "key2" => "text2"}
def text_for_something_selectable
##my_select_something_data[field_that_stores_id]
end
end
Then you can use it in your views like
#my_object.text_for_something_selectable
There are many possible variations of this. But this should work and you would have all information in a central place.
Update
Ok, I used something similar for our website. We need to store return_headers for rma. Those need to store a return reason as a code. Those codes are defined in an external MS SQL Server Database (with which the website exchanges lots of data, like orders, products, and much more). In the external db table are much more return reasons stored than I actually need, so I just took out a few of them. Still must make sure, the codes are correct.
So here goes he model:
class ReturnHeader < AciveRecord::Base
##return_reason_keys = {"010" => "Wrong Produc",
"DAM" => "Damaged",
"AMT" => "Wrong Amount"}
def self.return_reason_select
##return_reason_keys.invert
end
def return_reason
##return_reason_keys[nav_return_reason_code]
end
end
Model contains more code of course, but that's the part that matters. Relevant here is, that keys in the hash are strings, not symbols.
In the views i use it like this:
In the form for edit:
<%= form_for #return_header do |form| %>
<%= form.label :nav_return_reason_code "Return Reason" %>
<%= form.select :nav_return_reason_code, options_for_select(ReturnHeader.return_reason_select, #return_header.nav_return_reason_code) %>
<% end %>
(Maybe no the most elegant way to do it, but works. Don't know, why options_for_select expects a hash to be "text" => "key", but that's the reason, why above class level method returns the hash inverted.)
In my index action the return reason is listed in one of the columns. There I can get the value simply by
#return_headers.each do |rh|
rh.return_reason
end
If you have trouble to get it run, check that keys a correct type and value. Maybe add some debug info with logger.info in the methods to see what actual data is used there.

How can I format the value shown in a Rails edit field?

I would like to make editing form fields as user-friendly as possible. For example, for numeric values, I would like the field to be displayed with commas (like number_with_precision).
This is easy enough on the display side, but what about editing? Is there a good way to do this?
I am using the Rails FormBuilder. Upon investigation, I found that it uses InstanceTag, which gets the values for fields by using <attribute>_value_before_type_cast which means overriding <attribute> won't get called.
The best I have come up with so far is something like this:
<%= f.text_field :my_attribute, :value => number_with_precision(f.object.my_attribute) %>
Or my_attribute could return the formatted value, like this:
def my_attribute
ApplicationController.helpers.number_with_precision(read_attribute(:my_attribute))
end
But you still have to use :value
<%= f.text_field :my_attribute, :value => f.object.my_attribute %>
This seems like a lot of work.
I prefer your first answer, with the formatting being done in the view. However, if you want to perform the formatting in the model, you can use wrapper methods for the getter and setter, and avoid having to use the :value option entirely.
You'd end up with something like this.
def my_attribute_string
foo_formatter(myattribute)
end
def my_attribute_string=(s)
# Parse "s" or do whatever you need to with it, then set your real attribute.
end
<%= f.text_field :my_attribute_string %>
Railscasts covered this with a Time object in a text_field in episode #32. The really clever part of this is how they handle validation errors. It's worth watching the episode for that alone.
This is an old question, but in case anyone comes across this you could use the number_to_X helpers. They have all of the attributes you could ever want for displaying your edit value:
<%= f.text_field :my_number, :value => number_to_human(f.object.my_number, :separator => '', :unit => '', :delimiter => '', :precision => 0) %>
There are more attributes available too: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html
If you want a format to be created or maintained during editing, you will need to add Javascript to implement "masks." Here is a demo.
It was the first hit in these results.
You can use the number_format plugin. By specifying a number_format for an existing numeric attribute inside your model, the attribute will now appear as formatted to Rails in all forms and views. It will also be parsed back from that format (when assigned via forms) prior to insertion into the database. (The plugin also creates purely numeric unformatted_<attribute-name> accessors which can continue to be used for arithmetic, or for direct numerical assignment or retrieval by you for seamless integration.)
class MyModel < ActiveRecord::Base
# this model has the balance attribute, which we
# want to display using formatting in views,
# although it is stored as a numeric in the database
number_format :balance,
:precision => 2,
:delimiter => ',',
:strip_trailing_zeros => false
def increment_balance
unformatted_balance += 10
end
You can also combine the above with a Javascript solution, which can force the user to maintain the decimal point and thousands separators in place while editing, although this is really not necessary.
I have done something similar. We format times and lengths using a custom form builder. It makes use of the existing text_field, but wraps it so the value can be customized:
class SuperFormBuilder < ActionView::Helpers::FormBuilder
include ApplicationHelper
include FormHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
def length_field(label,*args)
scale = 'medium'
args.each do |v|
if v.has_key?(:scale)
scale = v[:scale]
v.delete(:scale)
end
end
value = length_conversion(#object.send(label.to_sym),scale)
options = (args.length > 0) ? args.pop : {}
return has_error(label, text_field_tag(field_name(label),value,*args) + ' ' + length_unit(scale))
end
private
def field_name(label)
return #object_name + "[#{label}]"
end
def has_error(label, output)
return "<div class='fieldWithErrors'>#{output}</div>" if #object.errors[label]
return output
end
And it is used like this:
<%= form_for( #section, {:action => 'save', :id => #section.id}, :builder => SuperFormBuilder) do |sf| %>
<%= sf.length_field :feed_size_min_w, :size => 3, :scale => 'small' %>
<% end %>
The end result is a value in the appropriate unit based off their choice on system (Metric, Imperial) and scale IE small = inches or millimeters.
I basically copied the text_field method from the existing form builder, which uses the text_field_tag itself.
There are two gotchas: 1) Knowing the name of the object field and how to access the object to get the value which you want to format. 2) Getting the name right so when the form is submitted it is part of the correct params hash.
The form builder is given a class variable #object. You can get the value of the field using the .send method. In my case I send the label :feed_size_min_w to the #object and get its length back. I then convert it to my desired format, and give it to the text_field_tag.
The name of the field is key to having it end up in the params hash, in my instance the params[:sections] one. I made a little helper function called field_name that takes care of this.
Finally the has_error wraps the field in an error div if there are errors on that label.
I needed "nicer" format on some specified text fields, resolved it by adding this to my initializers. Seems to work nicely on Rails ~= 5.2 and it should be easy to customize.
class ActionView::Helpers::Tags::TextField
private
def value_before_type_cast # override method in ActionView::Helpers::Tags::Base
v = super
# format as you like, when you like
if #options.delete(:nice_decimal)
v = v.to_s.gsub('.', ',') if v.is_a?(BigDecimal)
end
v
end
end
Usage in form f
<%= f.text_field :foo, nice_decimal: true %>

Resources