Reputation: 2223
I'm writing an app that acts as a tailor measure form.
The customers model has a lot of attributes that are stored in the database as integers in millimeters. Since this app will be used both in Europe and in the US I'll use virtual attributes for showing the user a inches and a centimeter version of the data.
For example for the customer height I have this in my model:
def height_in_cm
height / 10
end
def height_in_cm=(height)
self.height = height.to_f * 10
end
def height_in_in
height * 0.039370
end
def height_in_in=(height)
self.height = height.to_f / 0.039370
end
And this in my _form view:
<% if @customer.measure_unit.eql? "imperial" %>
<%= f.input :height_in_in %></br>
<% else %>
<% if @customer.measure_unit.eql? "metric" %>
<%= f.input :height_in_cm %></br>
<% end %>
<% end %>
Since as I said I have many attributes my customer model file is becoming extremely long and very error prone.
Is there a dry way to shorten it up?
Upvotes: 1
Views: 206
Reputation: 18784
Encapsulate that logic something like this:
def display_height
case measure_unit
when 'imperial' then height_in_in
when 'metric' then height_in_cm
else height_in_in
end
end
Then your view can just be like this:
<%= @customer.display_height %>
If you are using these same conversion methods in lots of models, extract it out to a module like so:
module HeightConversions
def height_in_cm
height / 10
end
def height_in_in
height * 0.039370
end
def display_height
case measure_unit
when 'imperial' then height_in_in
when 'metric' then height_in_cm
else height_in_in
end
end
end
And include it like so in the models where needed:
class Customer
include HeightConversions
end
EDIT:
Ok, perhaps you need something more like this:
%w(neck waist arm).each do |name|
self.class_eval do
define_method :"#{name}_in_cm" do
self.send(name) / 10
end
define_method :"#{name}_in_cm=" do |n|
self.send("#{name}=", (n.to_f * 10))
end
define_method :"#{name}_in_in" do
self.send(name) * 0.03
end
define_method :"#{name}_in_in=" do |n|
self.send("#{name}=", (n.to_f / 0.039370))
end
end
end
This is usually called metaprogramming. Here's a nice concise article on different ways to do it in Rails: http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html
Upvotes: 1
Reputation: 5362
I would let either a gem or javascript do the conversion. A gem such as ruby-units might be overkill but provides a lasting solution for any conversion you could want.
For letting javascript do the conversion, here's something you could add to your application.js. You could also use the "baseline" concept to DRY your method in rails.
function convert_units(input, input_unit, output_unit) {
// Used to convert all units to similar intermediary unit. I used meters.
var inches_per_meter = 39.3701;
var sixteenths_per_meter = 629.9216;
var cm_per_meter = 100;
var mm_per_meter = 1000;
var intermediary = 0; //in meters
switch(input_unit)
{
case: "mm": intermediary = input * mm_per_meter;
break;
case: "cm": intermediary = input * cm_per_meter;
break;
case: "meter": intermediary = input;
break;
case: "inches": intermediary = input * inches_per_meter;
break;
case: "sixteenths": intermediary = input * sixteenths_per_meter;
break;
default: return "Input unit not correctly accomodated";
}
switch(output_unit)
{
case: "mm": return intermediary / mm_per_meter;
break;
case: "meter": return intermediary;
break;
case: "inches": return intermediary / inches_per_meter;
break;
case: "sixteenths": return intermediary / sixteenths_per_meter;
break;
case: "cm": return intermediary / cm_per_meter;
break;
default: return "Output unit not correctly accomodated";
}
}
Then in your views
convert_units(@customer.height, 'inches', 'cm');
Upvotes: 0