Deck Builder App. How do I query only decks you can make with the cards in your inventory?
I'm making a deck building site similar to hearthpwn.com. 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.
Models
class Card
has_many :user_cards
has_many :users, through: :user_cards
has_many :deck_cards
has_many :decks, through: :deck_cards
end
class User
has_many :decks
has_many :user_cards
has_many :cards, through: :user_cards
end
class Deck
belongs_to :user
has_many :deck_cards
has_many :cards, through: :deck_cards
user_id
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|
deck.where(
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)) \
.exists
)
end
}
end
class UserCard
belongs_to :user
belongs_to :card
card_id
user_id
quantity
end
class DeckCard
belongs_to :deck
belongs_to :card
card_id
deck_id
quantity
end
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:
DeckCard.select{ |k| decks[:id].includes?(k.id) && 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: https://github.com/MatiasFMolinari/example_deck_builder
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
end
deck_id = deck_card_status.include?(false) ? '' : deck.id
deck_ids << deck_id
end
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 :)