How To Implement Player Levels Based on Points

I’ve been building an application with a lot of gamification recently and I’m looking for a little feedback on implementing player levels.

Users enter into competitions by submitting text responses(captions) to funny images(memes) and other users vote on the responses. After the voting period is over, a winner is selected based on number of votes. Points are awarded for submitting responses, voting on submissions, and winning competitions. The amount of points a user has will determine their level. Levels belong to tiers(3 in total), which will restrict users from entering into competitions for users of a certain tier level or above.

Tier 1 => Levels 1-5
Tier 2 => Levels 6-10
Tier 3 => Levels 11-15

Current Setup

class User belongs_to :player_level

class PlayerLevel has_many :users belongs_to :tier

class Tier has_many :player_levels

Has anyone implemented a flexible level system in Rails before? I’m trying to avoid littering my code with callbacks when I distribute points. Any feedback would be helpful!

It seems that points, level, and tier all refer to the same quantity and are simply multiples of each other. Just as 63,360 inches, 5,280 feet, and 1 mile are all different ways of expressing the same “thing”.

Persisting the same thing in multiple places creates redundancy, which could lead your system being in an inconsistent state (e.g. points/levels/tiers don’t match up correctly). Attempting to ensure that these stay in sync is a major headache.

Instead, you could just store one of these values and then calculate the others from that stored value. User could have a points attribute and tier and level could be calculated properties. For example:

class User
  def level
    # this formula could be a lot more complex
    ( points / 100) + 1

 def tier
    ( (level - 1) / 5 ) + 1

Now you only need to update the points and the other values instantly reflect the change.

user = User.create(points: 150)

#=> 1
#=> 1

user.update_attributes(points: 850)

#=> 9
#=> 2