Height is stored in the database in inches.
However feet and inches need their own individual inputs in the form:
Height: [_______] feet [_______] inches
So I used virtual attributes, and got it working. Here is a simplified version of my model:
class Client < ActiveRecord::Base
attr_accessible :name, :height_feet, :height_inches
before_save :combine_height
def height_feet
height.floor/12 if height
end
def height_feet=(feet)
@feet = feet
end
def height_inches
if height && height%12 != 0
height%12
end
end
def height_inches=(inches) #on save?
@inches = inches
end
def combine_height
self.height = @feet.to_d*12 + @inches.to_d #if @feet.present?
end
end
And the _form
partial using simple_form:
<%= simple_form_for(@client) do |f| %>
<ul>
<%= f.error_notification %>
<%= f.input :name %>
<%= f.input :weight %>
<li>
<%= f.input :height_feet, :label => 'Height', :wrapper => false %>
<span>feet</span>
<%= f.input :height_inches, :label => false, :wrapper => false %>
<span>inches</span>
</li>
<%= f.error :base %>
</ul>
<%= f.button :submit %>
<% end %>
This works. But it is not ideal.
I'd like to DRY this up and create a custom input component so I can add height to the form with <%= f.input :height, as: :feet_and_inch %>
—and therefore any other input that follows the same pattern such as <%= f.input :wingspan, as: :feet_and_inch %>
.
I've experimented with custom components, but I can't get two inputs to display—and I'm not sure where is the best place to put the 'conversion' logic from feet and inches to inches (and likewise from inches back to feet and inches).
As far as I know, you can't really move anything but the rendering to custom input. SimpleForm doesn't get called once the form is submitted so it can't really interfere with the values in any way. I would love to be wrong about this as I needed it in the past also. Anyway, here's a version that keeps the conversion logic in the model.
The custom SimpleForm input:
# app/inputs/feet_and_inch_input.rb
class FeetAndInchInput < SimpleForm::Inputs::Base
def input
output = ""
label = @options.fetch(:label) { @attribute_name.to_s.capitalize }
feet_attribute = "#{attribute_name}_feet".to_sym
inches_attribute = "#{attribute_name}_inches".to_sym
output << @builder.input(feet_attribute, wrapper: false, label: label)
output << template.content_tag(:span, " feet ")
output << @builder.input(inches_attribute, wrapper: false, label: false)
output << template.content_tag(:span, " inches ")
output.html_safe
end
def label
""
end
end
The form. Note that I did not put the <li>
tags inside the custom input, I think this way it's more flexible but feel free to change it.
# app/views/clients/_form.html.erb
<li>
<%= f.input :height, as: :feet_and_inch %>
</li>
All of this relies on the fact that for every height
attribute, you also have height_feet
and height_inches
attributes.
Now for the model, I am not honestly sure if this is the way to go, maybe someone might come up a better solution, BUT here it goes:
class Client < ActiveRecord::Base
attr_accessible :name
["height", "weight"].each do |attribute|
attr_accessible "#{attribute}_feet".to_sym
attr_accessible "#{attribute}_inches".to_sym
before_save do
feet = instance_variable_get("@#{attribute}_feet_ins_var").to_d
inches = instance_variable_get("@#{attribute}_inches_ins_var").to_d
self.send("#{attribute}=", feet*12 + inches)
end
define_method "#{attribute}_feet" do
value = self.send(attribute)
value.floor / 12 if value
end
define_method "#{attribute}_feet=" do |feet|
instance_variable_set("@#{attribute}_feet_ins_var", feet)
end
define_method "#{attribute}_inches=" do |inches|
instance_variable_set("@#{attribute}_inches_ins_var", inches)
end
define_method "#{attribute}_inches" do
value = self.send(attribute)
value % 12 if value && value % 12 != 0
end
end
end
It basically does the same but defines the methods dynamically. You can see at the top there's a list of attributes for which you want these methods to be generated.
Note that all of this is not really thoroughly tested and might kill your cat but hopefully can give you some ideas.
My humble opinion is that you would give better user experience if the user inputs the data in just one field . Here are my concerns :
Assuming you are using heights in limited range (probably human's height) , you can write a validation that detects what is the user input - inches or feet . Then you could make a validation link (or better a button ) asking if the input is what it meant to be (inches or feet detected) .
All this (including the dimension transformation while it's just inches -> feet) can be done in javascript , you can fetch the current dimensions by Ajax call and avoid reloading the whole code of the page .
EDIT : I've found an interesting point of view related with complicated inputs . Another useful resource about user interaction in filling form with feet and inches .
Your question is really interesting and I would love to see the solution you choose .
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With