Karan Edikala
Karan Edikala

Reputation: 1

How to show a calculation in controller to the view (Rails)

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

Answers (2)

johnnyh
johnnyh

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

jvillian
jvillian

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

Related Questions