Reputation: 89
I'm fairly new to this, so apologies in advance if this is a dumb question, or if I should be approaching this in a different way. I'm certainly open to almost all suggestions!
I'm working on creating a Jeopardy backend API using Rails 4. Currently, I've seeded my database with about 100 or so Categories, and each Category has multiple Clues, which house the question, answer and value. I also have a Game model which, when created, randomly selects up to 5 Categories and their clues.
My issue is that I want to know if there is a way to make sure that each Game only has five clues, and no duplicating values (ie, I don't want 5 clues that are all worth $100). I'm not sure if this is possible to do on the backend or if I should just filter it out on the frontend (I'm not using any Views - I'm building a separate dedicated JS client that will use AJAX to get the data from the API), but I feel like there has to be a Rails way to do this!
I'm not using any Join tables - Clues belongs_to Categories, and Categories has_many Clues, then a Game has_many Categories, so it's a pretty straight tree structure. I'm not sure what code would be helpful here for reference, since my Category and Clue models are pretty bare-bones at the moment, but here is what my Game model looks like:
class Game < ActiveRecord::Base
after_create :assign_category_ids, :create_response, :reset_clues
has_many :categories
has_one :response, as: :user_input
belongs_to :user
# On creation of a new game, picks a user-specified
# number of random categories
# and updates their game_ids to match this current game id
def assign_category_ids
game_id = id
num_cats = num_categories.to_i
@categories = Category.where(id: Category.pluck(:id).sample(num_cats))
@categories.map { |cat| cat.game_id = game_id }
@categories.each(&:save)
end
# Creates response to calculate user answer
def create_response
build_response(game_id: id, user_id: user_id)
end
# Resets categories and clues on every new game
def reset_clues
@categories = Category.where(game_id: id)
@categories.each do |category|
category.clues.each { |clue| clue.update_attributes(answered: false) }
category.update_attributes(complete: false)
end
end
end
Any advice at all will be greatly appreciated!! Thank you in advance!
Upvotes: 1
Views: 54
Reputation: 239311
I think there is some confusion in your underlying data model.
You're effectively mixing together two things that should be kept separate: The definition of categories and clues which you can think of as "static" data, and the game/response that users create, which is "dynamic" data.
A simplified (validation-free) implementation of your model layer should look something like this:
# This is pure data, it's the definition of a category
class Category
has_many :clues
# name: string
end
# This is pure data, it's the definition of a category, it's not tied to any user or game
class Clue
belongs_to :category
# answer: string
# question: string
end
# This ties a specific user to a set of clues through GameClue
class Game
belongs_to :user
has_many :game_clues
end
# This ties together a Game, a Clue and the user's inputted answer
class GameClue
belongs_to :game
belongs_to :clue
belongs_to :inputted_user_answer # Nil until a user inputs an answer
end
The key thing here is that Categories and Clues should never change. A Clue is the definition of a Clue, and many users may submit responses to it as part of many different games. The answer to this often-encountered problem is to create a completely separate type of record to hold user's responses, in this case GameClue
: It joins together a Game, a Clue and a user's response, however you wind up recording that.
The idea here is that you can have as many Games as you want, with each Game sharing many of the same Clues and Categories, something that cannot be accomplished if a Game "takes ownership" of a specific Clue and uses that record to store the user's answer.
Regarding your original question about validation, and following along form the above data model, I would do something like the following untested code:
class Game
def self.create_for(user)
user.games.create do |game|
[100,200,300,400,500].each do |points|
Category.where(id: Category.pluck(:id).sample(5)).map do |cat|
game.game_clues.create(clue: cat.clues.where(points: points).offset(rand(cat.clues.length)).first
end
end
end
end
validates :game_clues, length: 5
validate :must_have_clues_from_5_categories
validate :must_have_proper_range_of_points
protected
def must_have_clues_from_5_categories
if game_clues.pluck(:category_id).uniq.length < 5
errors.add(:game_clues, :invalid)
end
end
def must_have_proper_range_of_points
if game_clues.pluck(:points).uniq.length < 5
errors.add(:game_clues, :invalid)
end
end
end
The takeaway here is that you can use validate :method_name
to supply a custom validation method that can check for complex conditions and add errors to the object, preventing it from being saved.
Upvotes: 1
Reputation: 1035
You can user has_many :clues through :categories
and then validates :clues, length: { maximum: 5 }
Upvotes: 0