-2

I have a model Badges:

t.string :title
t.string :description
t.string :image
t.integer :points

And a model User:

t.string :first_name
t.string :last_name
t.integer :total_points
t.integer :used_points

What I need is to add badges to the user so that users can see/view what badges they have and if they already collected the points on it or not. Thank you!

jvillian
  • 19,953
  • 5
  • 31
  • 44
some thing
  • 137
  • 5
  • 2
    That's what associations are for. You'll want to use `has_many :through` or `has_and_belongs_to`. I prefer the former. A lot of folks use the latter. – jvillian Feb 13 '19 at 06:54

1 Answers1

2

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
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • You have added `collected` columns also, can you elaborate when it will get set `true` – ray Feb 13 '19 at 07:19
  • @ray Sure, I added a method to show how to do that. – Schwern Feb 13 '19 at 07:26
  • @Schwern So the format would be 1 record: user_badges = badge_id, user_id, collected. – some thing Feb 13 '19 at 07:35
  • @something One record per User/Badge combo. If a User 1 has 3 Badges you'd have 3 UserBadges: `{ user_id: 1, badge_id: 1, collected: false }, { user_id: 1, badge_id: 2, collected: false }, { user_id: 1, badge_id: 3, collected: false }`. – Schwern Feb 13 '19 at 07:42
  • 1
    @Schwern copy that. Thank you for your help! Salute! – some thing Feb 13 '19 at 07:45
  • I would like to add. For some reason I don't know why I'm getting an error when I've used "UsersBadge" as model I'm getting an error "badges_users not found on development database" so I've decided to change it to "BadgesUser" and no its running without error. – some thing Feb 19 '19 at 06:18
  • @something My mistake. [`has_and_belongs_to_many` assumes there's a table named in plural and alphabetical order](https://stackoverflow.com/questions/11590469/rails-naming-convention-for-join-table). It should be `badges_users`. – Schwern Feb 19 '19 at 08:47