How to connect models in a form without nesting? - ruby-on-rails

I did a lot of searches for this problem and watched quite a few tutorials (e.g railscast: nested model form) but I'm afraid it's not the right solution to my problem.
I want to collect data for a theory. This theory has many questions to be answered on distinctive cards. I always want to display all questions (on all cards), but the answers may vary, depending on each card. Therefor I need to catch the question_id and card_id, but deep nesting and catching the id by params would be quite a pain. Does anyone know a better solution for my problem?
Here are my models:
theory.rb
class Theory < ActiveRecord::Base
has_many :cards
has_many :questions
card.rb
class Card < ActiveRecord::Base
belongs_to :theory
has_many :answers
has_many :questions (Do I need this here - I always want to display all questions?)
question.rb
class Question < ActiveRecord::Base
belongs_to :theory
has_many :answers
answer.rb
class Answer < ActiveRecord::Base
belongs_to :card
belongs_to :question
Thank you a lot for your help!

The relationships between your models should not be determined by how they will be displayed on the screen. They should be determined by how they are actually related to each other.
I don't fully understand what theory/card are supposed to represent so I can't tell you what relationships need to exist, but I can have a stab at at it :
It seems to me as though a card represents the answers for a single person that relate a to a specific Theory? If so, then you don't need the "has_many :questions" because the card only has answers. Also, each answer links to a question so you can "get" to the questions through the answers. You could add a "has_many :questions, :through=>:answers" to the Card model if you really wanted the list of questions for a card.
If each card can have a different set of questions added to it before any answering is done, then you will need the "has_many :questions", but if all the questions are on each card then you won't need it.
Update in response to your comments below :
One way is to add the answers to the card by building them up in the controller:
#card = Card.find 2
#theory = Theory.find 1
#theory.questions.each do |question|
#card.answers.build :question=>question
end
Then in your view do something like this (this is HAML) :
= form_for #card do |f|
-#card.answers.each do |answer|
=f.fields_for answer do |answer_form|
=answer_form.hidden_field :question_id
%p=answer.question.full_question
%p=answer_form.text_field :input
This is using nested form attributes, so to get it to work with your Card model you'll have to add this to it :
class Card < ActiveRecord::Base
..
accepts_nested_attributes_for :answers
..
end

Related

Creating and querying questions/answers in Ruby-on-rails

We're creating a Ruby-on-rails application where the user is presented with a series of questions where they can give multiple different answers to one question. For now it's it's toggles but in the future we might be interested in doing freeform text as well. Each group of answers is connected to a pin on a map.
For now, each pin simply has a coplumn of questions and answers, which is a hash, but we'd really like to make it nicer and more scaleable. Some of the queries we'd like to do are e.g. count the amount of people that have answered 'It's quiet here' to the question 'Why do you come here?'
For now, I thought of the following models:
# pin.rb
class Pin < ApplicationRecord
has_many :questions
end
# question.rb
class Question < ApplicationRecord
belongs_to :pin
has_many :answers
end
# answer.rb
class Answer < ApplicationRecord
belongs_to :questions
end
Is this the right way of approaching this? I'd then fill seed.rbwith the questions and possible corresponding answers.
What's the best way of then creating new objects or rows, when a user answers a given question?
This looks good to me. If your Answer defines possible choices for a question, then for storing user answers/selections, you'll have to create another model/table
class UserAnswer < ApplicationRecord
belongs_to :question
belongs_to :answer
# attribute :value
end

Scaling large data in Rails4

I'm making a Q&A site. I have three models for now:
Question
Answer
User
Both Question and Answer can be voted for, so I need a way to store who voted on the model, to prevent multiple votes on one model by same user.
Would be a good idea to create a vote model? Where either the question_id or the answer_id will be empty in each. I'm afraid that this will create a lot of junk and slow the application down.
My other idea is to store the user_id in a hash in the Answer and in the Question.
If the user is already present in the hash it prevents voting. Or store the answer and question ids in the user.
What would be the Rails4 way to be fast but store most user interactions in models. What can be KISS, DRY and fast?
You should use a polymorphic model if you have multiple models the Vote can refer to.
class Vote < ActiveRecord::Base
belongs_to :voteable, polymorphic: true
end
class Question < ActiveRecord::Base
has_many :votes, as: :voteable
end
class Answer < ActiveRecord::Base
has_many :votes, as: :voteable
end
I don't think you have to worry about the speed for quite a while if you use indexes on the associations. Just put this snippet in a migration and you should be good to go!
add_index :votes, [:voteable_name, :voteable_id]

How to create voting to a multiple choice application using ruby on rails

I am working on a multiple choice question and answer application using Ruby on Rails and I have the following model.
class User < ActiveRecord::Base
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :user
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :answer
end
My problem is a user can choose all the answers.
How can I fix it so that it updates to the new answer?
For example, a has 5 votes and b 3 votes.
User clicks on a and a increments to 6 votes, same user comes back and clicks on b, a decrements back to 5 votes and b increments to 4 votes.
My first guess is that I need to add another model for example, user_choice with user_id and vote_id to track the previous answer.
You have your answer within the models. Just add a point system to the question model.
Under the question model,
def points
self.answers.count
end
Or, if answers have different values attached to them, make points an attribute of the answer instances.
def points
self.answers.pluck(:points).inject(:+)
end
This will sum up all the points from an answer belonging to a question, which allows you to order the questions by point count.
However, I am assuming you will need a Choice model for a question, unless that is in fact what the votes are for.
question has_many choices
choices belong_to question, etc.
I'm not sure what you mean when you say:
How can I fix it so that it updates to the new answer?
EDIT
Er, okay so you just need exactly what I mentioned earlier. A vote is really just the number of times an answer has been chosen.
If your problem is that users can choose more than 1 answer, it is likely that you should implement view based validations via javascript, or better yet, just disable their ability to select multiple choices, which if you were using a select tag, is actually the default. You must've added
multiple: true
in the select tag options for the user to select multiple entries.
On the backend, you can do this:
class Choice < ActiveRecord::Base
validates_uniqueness_of :user_id, scope: [:question_id, :answer_id]
end

Trying to get counts through multiple associations

I have created a question an answer app for a client much like StackOverflow. I am not trying to implement some sort of point system (like SO reputation). I am trying to get certain record counts through associations (which I believe are set up correctly). Primarily I am trying to get counts for votes on users answers. Here is an example.
In /views/questions/show page I list all the answers to that question by calling a partial _answer.html.erb. With each answer I pull in the answer.user information (username, email, etc.) by simply doing answer.user.username. I am wanting to display in a badge like format some total point calculations. So if User A answered Question A, next to User A's answer I want to display a total of all User A's answer votes.
I can successfully get a count for a users answers in /views/answers/_answer.html.erb by doing the following:
<%= answer.user.answers.count %>
but when I try to extend that syntax/association to get a count of votes on all User A's answers I get undefined method errors.
<%= answer.user.answers.votes.count %>
Is my set up fundamentally wrong here or am I missing something.
That is a bit confusing so let me know if you need more detail.
UPDATE:
Here are the associations:
Answers
class Answer < ActivRecord::Base
belongs_to :question
belongs_to :user
has_many :votes, :dependent => :destroy
end
Votes
class Vote < ActiveRecord::Base
belongs_to :answer
belongs_to :user
belongs_to :question
end
Users
class User < ActiveRecord::Base
has_many :questions, :dependent => :destroy
has_many :answers, :dependent => :destroy
has_many :votes, :through => :answers , :dependent => :destroy
end
<%= answer.user.answers.votes.count %>
but
answer.user.answers
is an array of answers so I suppose you wanted something like
<%= answer.user.answers[id].votes.count %>
UPDATE
<% answer.user.answers.each do |answer| %>
<%= answer.votes.count%>
<% end% >
UPDATE
<%= (answer.user.answers.map{|x| x.votes.count}).sum%>
I LOVE rails for such things
but when I try to extend that syntax/association to get a count of votes on all User A's answers I get undefined method errors.
You can find complete listing of what you can do with user.answers collection in here (besides standard Array/Enumerable methods). And it's only collection instance, not Answer object, so you can't invoke methods from Answer model on it.
You can try setupping has_many :votes, :through => :answers relationship. (See here for details) Although, I'm not sure if :through would work in such case.
Alternatively, you can create a method in User model to return all Vote objects (simply by iterating through all answers)
But frankly, creating a horde of Vote objects simply to count them sounds like a terrible waste of resources to me.

Should this even be a has_many :through association?

A Post belongs_to a User, and a User has_many Posts.
A Post also belongs_to a Topic, and a Topic has_many Posts.
class User < ActiveRecord::Base
has_many :posts
end
class Topic < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :topic
end
Well, that's pretty simple and very easy to set up, but when I display a Topic, I not only want all of the Posts for that Topic, but also the user_name and the user_photo of the User that made that Post. However, those attributes are stored in the User model and not tied to the Topic. So how would I go about setting that up?
Maybe it can already be called since the Post model has two foreign keys, one for the User and one for the Topic?
Or, maybe this is some sort of "one-way" has_many through assiociation. Like the Post would be the join model, and a Topic would has_many :users, :through => :posts. But the reverse of this is not true. Like a User does NOT has_many :topics.
So would this even need to be has_many :though association? I guess I'm just a little confused on what the controller would look like to call both the Post and the User of that Post for a give Topic.
Edit: Seriously, thank you to all that weighed in. I chose tal's answer because I used his code for my controller; however, I could have just as easily chosen either j.'s or tim's instead. Thank you both as well. This was so damn simple to implement. I think today might mark the beginning of my love affair with rails.
you can get the user_name and user_photo when displaying a topic and its posts...
something like:
#topic.posts.each do |post|
user_name = post.user.name
user_photo = post.user.photo
end
it's simple. sorry if i didn't understand your question very well.
Well, if what you want is display the user name of the author of a post, for example, you can do something like (not tested, but should work) :
#posts = topic.posts.all(:include => :user)
it should generate one request for all posts, and one for users, and the posts collection should have users.
in a view (haml here) :
- #posts.each do |post|
= post.user.name
= post.body
If I understand your question correctly, no, a has_many :through association is not required here.
If you wanted to display a list of all users that posted in a topic (skipping the post information), then it would be helpful.
You'll want to eager load the users from posts, though, to prevent n+1 queries.

Resources