All threads / Deck Builder App. How do I query only decks you can make with the cards in your inventory?
Ask A Question


You’re not receiving notifications from this thread.

Deck Builder App. How do I query only decks you can make with the cards in your inventory?

Matias Fernandez asked in Rails

I'm making a deck building site similar to It would be nice if users could filter decks based off the cards they have in their inventory.

In other words, I'm trying to make a query that gets the following:

  • All Decks where DeckCard.card_ids are all in UserCard.card_ids AND where deck_card.quantity is less than or equal to the matching user_card.quanitity.

I want to have a scope in the Deck Model that does this. You can see my current implementation below. See scope :with_all_deck_cards_in_user_cards. It doesn't filter the quantities correctly and I need some help debugging it.


class Card
    has_many :user_cards
    has_many :users, through: :user_cards
    has_many :deck_cards
    has_many :decks, through: :deck_cards

class User
    has_many :decks
    has_many :user_cards
    has_many :cards, through: :user_cards

class Deck
    belongs_to :user
    has_many :deck_cards
    has_many :cards, through: :deck_cards


    scope :with_all_deck_cards_in_user_cards, lambda { |user_id|
        user_cards = User.find(user_id).user_cards
        deck_cards = DeckCard.arel_table
        decks      = Deck.arel_table

        user_cards.reduce(self) do |deck, user_card|
                DeckCard \
                .where(deck_cards[:deck_id].eq(decks[:id])) \
                .where(deck_cards[:card_id].eq(user_card.card_id)) \
                .where(deck_cards[:quantity].lteq(user_card.quantity)) \

class UserCard
    belongs_to :user
    belongs_to :card


class DeckCard
    belongs_to :deck
    belongs_to :card


Let me know if you need any more information.

Hey Matias,

Is there any chance you could put together a quick example you could share on github? It's kind of hard to debug without being able to play with the data itself. When you say it's not filtering the quantities correctly, what is the result you're getting and what is the expected result?

Have you tried using some of the other enumerable methods to restructure the query? I remember having some weird results when I started chaining where statements, so I think (as memory serves) I had to switch to using select in that particular scenario... so something like:{ |k| decks[:id].includes?( && deck_cards[:card_id] == user_card.card_id && deck_cards[:quantity] <= user_card.quantity }

But my memory is pretty foggy on that...

I've prepared an example on github with seed data and expected results:

Requirements and expected results are in the index page.

To summarize, I need to create a scope in the Deck model that only returns the decks that that user can build. It cannot return an enumerable since I need to be able to chain it with other scopes.

Alright, well after a little tinkering I've got a working solution. It could be refactored quite a few times, but I like to first get a solution, then work on refactoring.

scope :that_user_can_create, -> (user_id) {
  user_cards = User.find(user_id).user_cards
  # no need to proceed if user doesn't have any cards
  return nil if user_cards.nil?

  deck_ids = []
  Deck.all.each do |deck|
    deck_card_status = []
    deck.deck_cards.each do |card|
      # check to see if the user has this card
      has_card = user_cards.where(card_id: card.card_id).present?
      deck_card_status << has_card
      # no need to proceed if the user doesn't have this card
      next unless has_card
      has_qty  = user_cards.where(card_id: card.card_id).first.quantity >= card.quantity
      deck_card_status << has_qty

    deck_id = deck_card_status.include?(false) ? '' :
    deck_ids << deck_id

  Deck.where(id: deck_ids.flatten.uniq)

The approach I'm taking is to compare each card in a deck to see if the user meets that cards requirements, if so, that card is flagged true in deck_card_status. deck_card_status then gets checked to see if it includes false, as long as it doesn't then you can include it in the deck_ids. Then, do the final Deck.where(id: deck_ids.flatten.uniq) to get the decks that user can make.

This works Jacob, thank you for taking the time to help me out. If you want to see it in production, take a look at YugiDecks in the coming weeks.

Just checking if this a typo when you past it.

  has_many :deck_cards, through: :deck_cards

did you mean?

`has_many :decks, through: :deck_cards`

Awesome, I'm glad it will work for you! I'll keep an eye out, look forward to seeing the completed site!

Yes, that was a typo Francisco - the repository he shared had the correct association in it. Good catch though, I never even noticed haha :)

Thanks Francisco! I corrected the typo.

Join the discussion

Want to stay up-to-date with Ruby on Rails?

Join 68,099+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    logo Created with Sketch.

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more. Icons by Icons8

    © 2022 GoRails, LLC. All rights reserved.