Reputation: 1
I am building a website that can store body measurements and calculate body fat using jackson/ pollock 7 method formula. I am pretty new to rails and not sure how I can output the calculation from the measurements controller into the show.html.
Measurements_controller.rb calculate method:
def calculate
bodyDensityMale = 1.112 - (0.00043499 * (@measurement.chest + @measurement.Midaxillary + @[email protected][email protected]
[email protected][email protected])) + (0.00000055 * (@[email protected][email protected][email protected][email protected]
[email protected][email protected])^2) - (0.00028826 * @measurement.age)
bodyDensityFemale = 1.097 - (0.00046971 * (@measurement.chest + @[email protected][email protected][email protected]
[email protected][email protected])) + (0.00000056 * (@[email protected][email protected][email protected][email protected]
[email protected][email protected])^2) - (0.00012828 * @measurement.age)
bodyFatMale = (495 / bodyDensityMale) - 450
bodyFatFemale = (495 / bodyDensityFemale) - 450
if (@measurement.gender_type = 'Male')
puts bodyFatMale
else
puts bodyFatFemale
end
end
show.html.erb
<p>
<strong>Body Fat (%): </strong>
<%= @measurement.calculate %>
</p>
When I create the entry there are no errors but the Body fat % field shows up empty. I appreciate any help!
Upvotes: 0
Views: 259
Reputation: 23
A few notes:
1) Be sure to follow Ruby naming conventions: use snake case for symbols, methods, and variables (e.g. bodyDensityMale
should be body_density_male
and Midaxillary
, Tricep
, etc. should be midaxillary
, tricep
)
2) ^
is the Ruby binary XOR operator. **
is for exponents.
3) There is a bug in your controller condition due to =
vs ==
.
if (@measurement.gender_type = 'Male')
should be
if (@measurement.gender_type == 'Male')
The first is assigning the value, so it will always be true. ==
is for equality comparison.
The calculation logic is best moved out of the controller and into a model or service class. Here are two solutions for putting it in the Measurement
model:
################################################################################
# Solution #1
################################################################################
class Measurement
MALE = 'Male'
def body_fat
(495 / body_density) - 450
end
def body_density
gender_type == MALE ? body_density_male : body_density_female
end
def body_density_male
1.112 -
(0.00043499 * body_density_sum) +
(0.00000055 * body_density_sum ** 2) -
(0.00028826 * age)
end
def body_density_female
1.097 -
(0.00046971 * body_density_sum) +
(0.00000056 * body_density_sum ** 2) -
(0.00012828 * age)
end
def body_density_sum
[chest, midaxillary, tricep, subscapular, abdominal, suprailiac, thigh].sum
end
end
################################################################################
# Solution #2 using subclasses
################################################################################
class Measurement
def body_fat
(495 / body_density) - 450
end
def body_density_sum
[chest, midaxillary, tricep, subscapular, abdominal, suprailiac, thigh].sum
end
end
class MaleMeasurement < Measurement
def body_density
1.112 -
(0.00043499 * body_density_sum) +
(0.00000055 * body_density_sum ** 2) -
(0.00028826 * age)
end
end
class FemaleMeasurement < Measurement
def body_density
1.097 -
(0.00046971 * body_density_sum) +
(0.00000056 * body_density_sum ** 2) -
(0.00012828 * age)
end
end
To use Solution #2 with subclasses you would have to have set up the Measurement
model and database table using single table inheritance. Solution #1 would require no database changes and is probably quickest/easiest (you could refactor down the road).
With either solution though, in your view:
<p>
<strong>Body Fat (%): </strong>
<%= @measurement.body_fat %>
</p>
Upvotes: 1
Reputation: 20263
It seems like your primary problem is that you're calling calculate
on @measurement
here:
<p>
<strong>Body Fat (%): </strong>
<%= @measurement.calculate %>
</p>
But, it looks like the calculate
logic is in the controller. I don't know what the calculate
method looks like on @measurement
, but apparently it's return nil
or some such thing.
If I were you, I'd move all this logic into a BodyFatCalculator
class, something like:
class BodyFatCalculator
# using a constant makes it a *little* easier to make sure you don't have
# a typo in your constant values
CONST_VALUES = {
male: {
one: 1.112,
two: 0.00043499,
three: 0.00000055,
four: 0.00028826
},
female: {
one: 1.097,
two: 0.00046971,
three: 0.00000056,
four: 0.00012828
}
}
# this just defines the methods 'const_one', 'const_two', etc. that
# return the appropriate constant from above. The const_val
# method is defined below.
[:one, :two, :three, :four].each do |const_ref|
define_method("const_#{const_ref}") do
const_val const_ref
end
end
# provides a getter/setter for @measurement
attr_accessor :measurement
class << self
# class-level method that lets you do BodyFatCalculator.calculate.
# Requires that a 'measurement' is passed in as an argument.
def calculate(measurement)
new(measurement).calculate
end
end
# sets the @measurement instance variable.
def initialize(measurement)
@measurement = measurement
end
# does the calculation
def calculate
(495 / body_density) - 450
end
# calculates body density. I think this is a *little* more readable
# so that it's easier to see if you have the formula right. The 'const_one',
# 'const_two', etc. methods were defined at the top.
# The `sum_two` method is defined below.
def body_density
const_one - ( const_two * sum_two ) + ( const_three * sum_two^2 ) + ( const_four * measurement.age )
end
# given a type (like ':one', ':two', etc.), uses 'measurement.gender_type'
# to return the correct constant.
def const_value(type)
CONST_VALUES[measurement.gender_type.underscore.to_sym][type]
end
# given an array of names, will sum the values from '@measurement'
# that correspond to the name.
def sum_of *names
names.each_with_object(0) do |name, sum|
sum += measurement.send(name)
end
end
# sum of a specific set of values from '@measurement'.
def sum_two
@sum_two ||= sum_of(*w(chest Midaxillary Tricep Subscapular Abdominal Suprailiac Thigh))
end
end
(WARNING: Hasn't been tested.)
Then, in your controller, do something like:
def show
@body_fat = BodyFatCalculator.calculate(@measurement)
end
And in your view:
<p>
<strong>Body Fat (%): </strong>
<%= @body_fat %>
</p>
Upvotes: 2