dax
dax

Reputation: 11017

how to DRY this code?

I have the following line of code in several of my models:

def average(scores)
  # get average of scores and round to two decimal places
  average = scores.inject{ |sum, el| sum + el }.to_f / scores.size
  average.round(2)
end

I've tried to put it in various helper files, with varying success - but the problem isn't that I can't getting working, it's that it takes some ugly code and/or extra files (modules, etc) just to include this method in all of models - and that's raising some red flags. It shouldn't be that hard.

Helper code is easy for controllers and views, but seems really counter-intuitive for models - at the same time, it seems silly to have (literally) the exact same code in 4 places. What's the best way to dry this out?

update

I want to use the average helper inside of each model's methods - which are different in every case, but for the last line where everything is averaged - like so:

def avg_for(student)
  scores = []
  self.evals.map do |student_id, evals|
    evals.select {student_id == student.id}.each do |eval|
      scores << eval.score
    end  
  end    
  average(scores) #here!
end

Upvotes: 2

Views: 190

Answers (2)

Damien
Damien

Reputation: 27493

http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-average

class Student < ActiveRecord::Base
  has_many :evals

  def average_score
    evals.average(:score)
  end
end

Outside of Rails:

def average(score)
  (score.inject(:+).to_f / score.size).round(2)
end

Edit

With your avg_for method:

def avg_for(student)
  evals.where(student: student).average(:score)
end

Upvotes: 2

kik
kik

Reputation: 7937

For this very specific method, you can use @delba answer.

To answer exactly your question about sharing methods across models, that's a concern job.

In rails-4, concerns becomes top level citizen, and directories app/models/concerns and app/controllers/concerns are automatically created.

You can add something like that, in app/concerns/averageable.rb :

module Averageable
  def average(scores)
    # get average of scores and round to two decimal places
    average = scores.inject{ |sum, el| sum + el }.to_f / scores.size
    average.round(2)
  end
end

Then, use it in your model :

class User < ActiveRecord::Base
  include Averageable
end

The methods from your concern will be available for any model that includes it.

Edit :

To do the same in rails-3, add the path you want to put your concerns in into config.autoload_paths, in config/application.rb :

config.autoload_paths += %W(#{config.root}/lib/concerns)

And put the averageable.rb module in that directory.

Upvotes: 1

Related Questions