Jay
Jay

Reputation: 86

Define method with arbitrary name

My rails application has an ActiveRecord model called Game which stores and relates information about a sport contest, including database items like home_team_id and home_team_score as well as methods like winning_team_id and winning_team_score which apply logic to the stored data.

I have another file in app/models/ which defines an independent class Record. A record is created by passing as parameters an array of games and a team_id to create instance variables such as @wins and @points_for for the team corresponding to the team_id.

Within Record I to define two instance methods average_points_for and average_points_against, which does exactly what you'd expect:

class Record
    def games_played
        return @wins + @losses
    end
    def average_points_for
        return (@points_for.to_f / games_played).round(2)
    end
    def average_points_against
        return (@points_against.to_f / games_played).round(2)
    end
end

Seems non-DRY to declare average_points_for and average_points_against as two different methods. I realize I could do this...

class Record
    def games_played
        return @wins + @losses
    end
    def average_points(which)
        return eval("(@points_#{which}.to_f / games_played).round(2)")
    end
end

...but average_points("for") looks ugly — I prefer the convention of average_points_for.

What I'd prefer is something like this:

class Record
    def games_played
        return @wins + @losses
    end
    def average_points_#{which}
        return (@points_#{which}.to_f / games_played).round(2)
    end
end

Is there a way to do this?

Upvotes: 1

Views: 73

Answers (1)

Amadan
Amadan

Reputation: 198304

In my opinion, for your use case it would be cleaner to just refactor it to DRY it up:

private def average(value)
  (value.to_f / games_played).round(2)
end

def average_points_for
  average(@points_for)
end

def average_points_against
  average(@points_against)
end

But, as comments indicate, you could use define_method. Rails does it to great benefit, as does Ruby itself in, say, OpenStruct; but it is an overkill in this case. If you really wanted to do it, this would be the way (untested, may contain bugs):

%i(for against).each do |which|
  define_method(:"average_points_#{which}") do
    (instance_variable_get(:"@points_#{which}").to_f / games_played).round(2)
  end
end

Upvotes: 4

Related Questions