Assuming you want many Users to have the same Badge, you need is a many-to-many association between Badge and User. A User can have many Badges, and a Badge can have many Users. This requires a join table to store which User has which Badges.
create_table :badges_users do |t|
t.belongs_to :user, index: true
t.belongs_to :badge, index: true
end
Since it's nothing but a list, no model for this table is required. Use has_and_belongs_to_many
.
class Badge < ApplicationRecord
has_and_belongs_to_many :users
end
class User < ApplicationRecord
has_and_belongs_to_many :badges
end
Adding a Badge to a User is as simple as pushing onto an array.
user.badges << badge
Or vice-versa.
badge.users << user
They do the same thing, add a row to badges_users
with the badge and user IDs.
See here for more about how to use these collections.
Instead of storing a User's points in User, calculate them from their Badges.
def total_points
badges.sum(:points)
end
If you need to track whether a User has "collected" a Badge yet or not, you'll need to store that in the join table and use a model to get that info.
create_table :badge_users do |t|
t.belongs_to :user, index: true
t.belongs_to :badges, index: true
t.boolean :collected, default: false
end
class BadgeUser < ApplicationRecord
belongs_to :user
belongs_to :badges
end
And then use has_many
and has_many :through
to set up the associations.
class User < ApplicationRecord
has_many :badge_users
has_many :badges, through: :badge_users
end
class Badge < ApplicationRecord
has_many :badge_users
has_many :users, through: :badge_users
end
Adding a badge to a user is the same as before, user.badges << badge
.
Then we let badges be collected by users.
# In BadgeUser
def collect
if collected
raise "Badge already collected"
end
update!(collected: true)
end
# In User
def collect_badge(badge)
badge_users.find_by( badge: badge ).collect
end
And users can find their collected badges.
# In User
def collected_badges
badges.merge(BadgeUser.where(collected: true))
end
Once a User can find their collected Badges they can total up their points to find out how many points they've used.
# In User
def used_points
collected_badges.sum(:points)
end