Reputation: 5335
I have an empty object that accepts calculated values for each factor. The method is readable but long and ugly. What is a DRY way of doing this?
class ReadingScore
def initialize(reading, score)
@reading = reading
@score = score
end
def assign_scoring_factors
@score.heart_rate_factor = heart_rate_factor
@score.systolic_pressure_factor = systolic_pressure_factor
@score.diastolic_pressure_factor = diastolic_pressure_factor
@score.mean_pressure_factor = mean_pressure_factor
@score.signal_minimum_factor = signal_minimum_factor
@score.signal_average_factor = signal_average_factor
…
end
def heart_rate_factor
@reading.heart_rate && (1..10).include?(@reading.heart_rate) ? 0 : 10
end
…
end
Update The overall purpose of this class is to calculate a score of a reading. I can’t provide all the code because it is a proprietary algorithm for a medical device.
But basically there are n factors of a @reading
that are calculated and then saved to a @score
object associated with the @reading
. The sum of the factors is also calculated as a total on the @score
object. The @score
object looks like the following:
@score=
#<Score:0x007faa0b33ec50
@attributes=
{"id"=>42,
"reading_id"=>42,
"sum_of_factors"=>10,
"heart_rate_factor"=>10,
"another_factor"=>0,
"another_factor"=>0}
Upvotes: 0
Views: 82
Reputation: 29478
You could use delegate
class ReadingScore
extend Forwardable
delegate [:heart_rate_factor=, :systolic_pressure_factor=,:diastolic_pressure_factor=,
:mean_pressure_factor=,:signal_minimum_factor=,:signal_average_factor=] => :@score
def initialize
@score = Score.new
end
def assign_scoring_factors
%w(heart_rate_factor systolic_pressure_factor diastolic_pressure_factor mean_pressure_factor signal_minimum_factor signal_average_factor).each do |meth|
self.send("#{meth}=",self.send(meth))
end
self
end
end
but I agree with others that rethinking the whole design might be better here.
You could also use tap
but the code will look fairly similar to what you have now.
Also I have no idea what a Score
looks like internally because it seems like it would be better to place this logic inside the Score
or Reading
and pass all of this to a method or initializer of Score
. e.g.
class ReadingScore
def intialize(reading)
@reading = Reading.new(reading)
@score = Score.new(@reading)
end
end
class Reading
#...
def heart_rate_score
heart_rate && (1..10).include?(@reading.heart_rate) ? 0 : 10
end
def systolic_pressure_score
#logic
end
def diastolic_pressure_score
#logic
end
def mean_pressure_score
#logic
end
def signal_minimum_score
#logic
end
def signal_average_score
#logic
end
end
class Score
attr_accessor :heart_rate_factor,:systolic_pressure_factor,:diastolic_pressure_factor,
:mean_pressure_factor,:signal_minimum_factor,:signal_average_factor
def initialize(reading)
factorialize(reading)
self
end
private
def factorialize(reading)
%w(heart_rate systolic_pressure diastolic_pressure mean_pressure signal_minimum signal_average) do |meth|
self.send("#{meth}_factor=",reading.send("#{meth}_score")
end
end
end
This way your logic is centralized in Score
and Reading
and can be avoided in ReadingScore
. This will make the code easier to trace and will clean up the original class.
Upvotes: 1
Reputation: 22365
You can automatically populate an array of factors using method_added
. This combines nicely with dynamic assignment of factors as in your answer.
class ReadingScore
@factors = []
def self.method_added meth
@factors << meth if meth =~ /_factor\Z/
end
def self.factors
@factors
end
end
Note that these are class methods, so you would need to use self.class.factors
when using this in an instance method.
Here is a full implementation in case you do not see how to integrate this code.
Upvotes: 2
Reputation: 5335
This seems to be the best option so far. The first answer to the question started me on this route, but the poster seems to have removed it…
def assign_factors_to_score
factors.each do |factor|
@score.public_send("#{factor}=", self.public_send(factor))
end
end
def factors
%i{factor_a factor_b factor_c factor_d}
end
Upvotes: 2
Reputation: 997
you can do it like this, if you insist:
def assign_scoring_factors
%w(heart_rate systolic_pressure diastolic_pressure mean_pressure signal_minimum signal_average).each |f| do
eval("@score.#{f}.factor = #{f}.factor")
end
end
but this isn't what I'd do. I'd either leave it moist, or just use a map.
Upvotes: -1