I have a model relationship set up like this:
# User model
class User < ActiveRecord::Base
belongs_to :company
has_many :answers
# groups
has_many :group_memberships
has_many :group_questions, through: :groups, source: :questions
has_many :groups, through: :group_memberships
# question
has_many :question_participants, as: :questionable
has_many :questions, through: :question_participants
# questions created by admin
class Question < ActiveRecord::Base
belongs_to :company
has_many :question_participants
has_many :answers
has_many :users, through: :question_participants,
source: :questionable, source_type: 'User'
has_many :groups, through: :question_participants,
source: :questionable, source_type: 'Group'
has_many :companies, through: :question_participants,
source: :questionable, source_type: 'Company'
end
# user answers to questions
class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
now my answer stores user_id, reply (their answer) and question id.
What I'd like to be able to create now is a form that allows a user to answer all the questions asked to them, and submit the answers.
I've setup my form like this on the view file (answers#new):
<%= form_for #answer do |f| %>
<% current_user.all_questions.each do |question| %>
<%= f.hidden_field :question_id, value: question.id %>
<p><%= question.name %>
<%= f.text_field :reply %>
<% end %>
<% end %>
which doesn't work, and I get why - issue is i don't know how to make it work with multiple save.
I hope I got your problem. Here is what I suggest:
<%= form_for #answer do |f| %>
<% current_user.all_questions.each do |question| %>
<%= hidden_field_tag 'questions[][id]', question.id %>
<p><%= question.name %>
<%= text_field_tag 'questions[][reply]' %>
<% end %>
<% end %>
Now in the controller action you can access params[:questions] and you'd get smth like this: [{"id"=>"1", "reply"=>"some text 1"}, {"id"=>"2", "reply"=>"some text 2"}]:
# your_controller.rb
def create_answers
answers = params[:questions].map do |question|
current_user.answers.create(question_id: question[:id], reply: question[:reply])
end
if answers.any(&:invalid?)
flash[:error] = 'Some answers were not accepted'
redirect_to :back
else
redirect_to home_path
end
end
Related
I try to create a validation that work in a nested for with a collection of checkboxes.
The collection is inside a each.do loop.
<%= simple_form_for #questionnaire do |f| %>
<% #questions.each do |question| %>
<%= question.name %>
<%= f.collection_check_boxes :answer_ids, Answer.where(question_id: question.id), :id, :content %>
<% end %>
<% end %>
This is inside a Questionnaire form.
So I try to validate answer in the Questionnaire model, but I need to do it for each question : each question should at least have a checkbox checked.
Any idea would be great!
Thank you in advance!
EDIT :
The relationship between my 3 models :
class Questionnaire < ActiveRecord::Base
has_many :answers
has_many :answers, :through => :answers
end
class Question < ActiveRecord::Base
has_many :questionnaires
has_many :answers, through: :questionnaires
end
class Answer < ActiveRecord::Base
belongs_to :question
has_many :questionnaires, :through => :answers
end
You need to validate answer_ids like below
validates :answer_ids, presence: true
This line put on your model
I think to help you
I have a nested form that handles a survey and its answers. But I'm getting a strange error when I load the form:
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly
Any ideas? I'm not sure how I should be fixing the associations.
<%= form_for #survey do |f| %>
...
<%= f.fields_for :answers do |builder| %>
<%= builder.text_field :content, :class=>"form-control" %>
<% end %>
...
<% end %>
Survey#new
def new
#survey = Survey.new
#template = Template.find(params[:template_id])
#patient = Patient.find(params[:patient_id])
#survey.answers.build
end
Survey.rb
class Survey < ActiveRecord::Base
belongs_to :template
has_many :questions, :through=> :template
has_many :answers, :through=> :questions
accepts_nested_attributes_for :answers
end
Template.rb
class Template < ActiveRecord::Base
belongs_to :survey
has_many :questions
end
Question.rb
class Question < ActiveRecord::Base
belongs_to :template
has_many :answers
end
Answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
end
You've missed the line has_many :templates in the Survey.rb.
Also you must specify :templates (plural of the model name) in the
same file:
has_many :questions, :through=> :templates
So final variant is:
class Survey < ActiveRecord::Base
has_many :templates
has_many :questions, :through=> :templates
has_many :answers, :through=> :questions
accepts_nested_attributes_for :answers
end
Also as your models survey and answer are associated, you don't need to get templates in controller:
def new
#survey = Survey.new
#patient = Patient.find(params[:patient_id])
#survey.answers.build
end
The example line in here didn't work. I tried 20 varieties of it.
views/comments/_comments.html.erb
<% #comments.each do |comment| %>
<%= User.find(comment.user_id).name %> # Gives user name of commenter
<%= simple_format comment.content %>
<%= pluralize(comment.likes, 'like') %>
<%= link_to content_tag(:span, '', class: 'glyphicon glyphicon-thumbs-up') +
' Like it', like_comment_path(:id => comment.id), method: :post %>
<%= User.find(like.user_id).name %> # How to get user name of liker?
<% end %>
comments_controller.rb
def like
#comment = Comment.find(params[:id])
comment_like = current_user.comment_likes.build(comment: #comment)
if comment_like.save
#comment.increment!(:likes)
#comment.create_activity :like
#user = User.find(params[:id])
flash[:success] = 'Thanks for liking!'
else
flash[:error] = 'Two many likes'
end
redirect_to(:back)
end
comment_like.rb
class CommentLike < ActiveRecord::Base
belongs_to :comment
belongs_to :user
validates :user, uniqueness: { scope: :comment }
end
comment.rb
class Comment < ActiveRecord::Base
include PublicActivity::Common
# tracked except: :update, owner: ->(controller, model) { controller && controller.current_user }
has_many :comment_likes
has_many :likers, through: :comment_likes, class_name: 'User'
belongs_to :commentable, polymorphic: true
belongs_to :user
end
A comment has many likes. So you are looking for the comment likers.
class Comment
has_many :comment_likes
has_many :likers, through: :comment_likes, class_name: 'User', source: :liker
end
class CommentLikers
belongs_to :liker, class_name: 'User', foreign_key: :user_id
belongs_to :liked_comment, class_name: 'Comment', foreign_key: :comment_id
end
class User
has_many :comment_likes
has_many :liked_comments, through: :comment_likes, class_name: 'Comment', source: :liked_comment
end
Then in your views:
<% #comments.each do |comment| %>
<% comment.likers.each do |user| %>
<%= user.name %>
<% end %>
<% end %>
EDIT 1
Your question is so well explained in the rails guides:
http://guides.rubyonrails.org/association_basics.html
But just to give you a short explanation:
You need to associate the comments through the comment likes to the users. To do you need to tell rails to look for the user in the table using the user_id.
That's what the through: :comment_likes, class: 'User' do.
EDIT 2
Check the source code to see more options:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many
1) If like variable declared in controller it should be global: #like
2) Don't use User.find(comment.user_id).name or User.find(like.user_id).name.
Use it like comment.user.name, #like.user.name
The short answer
In your controller, change
comment_like = current_user.comment_likes.build(comment: #comment)
if comment_like.save
with
#comment_like = current_user.comment_likes.build(comment: #comment)
if #comment_like.save
In your view, change
<%= User.find(like.user_id).name %>
with
<%= #comment_like.user.name %>
I have the scenario where an author has and belongs to many books, vice versa. Following the instructions for setting up associations in a one-to-many relationship works fine but when a many-to-many relationship introduced I get this error message whenever I try to create or update my book model.
undefined method `author' for #<Book:0x007fb91ae56a70>
As far as setting up how authors are chosen for a book I'm using the code provided by the token-input railscast here with a few alterations.
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
def self.tokens(query)
authors = where("name like ?", "%#{query}%")
if authors.empty?
[{id: "<<<#{query}>>>", name: "Add New Author: \"#{query}\""}]
else
authors
end
end
def self.ids_from_tokens(tokens)
tokens.gsub!(/<<<(.+?)>>>/) {create!(name: $1).id}
tokens.split(',')
end
end
class Book < ActiveRecord::Base
attr_reader :author_tokens
include PublicActivity::Model
tracked owner: :author
has_many :authorships
has_many :authors, through: :authorships
def author_tokens=(ids)
self.author_ids = Author.ids_from_tokens(ids)
end
end
Form View
<%= form_for(#book) do |f| %>
...
<div class="field">
<%= f.text_field :author_tokens, label: 'Author', input_html: {"data-pre" => #book.authors.to_json} %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
There is no author relationship in your Book model.
What
tracked owner: :author
does is basically calling method author on your Book instance. You should try :authors
But!
That won't solve your problem because owner can only be one. So you can do something like:
tracked owner: proc {|_, book| book.authors.first }
to set the owner to the first author the book has.
class Author < ActiveRecord::Base
has_many :author_books, inverse_of: :author, dependent: :destroy
accepts_nested_attributes_for :author_books
has_many :books, through: :author_books
end
class Book < ActiveRecord::Base
has_many :author_books, inverse_of: :book, dependent: :destroy
accepts_nested_attributes_for :author_books
has_many :authors, through: :author_books
end
class AuthorBook < ActiveRecord::Base
validates_presence_of :book, :author
end
============= view ==============
<%= form_for #book do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :author_books do |f2| %>
<%# will look through all author_books in the form builder.. %>
<%= f2.fields_for :author do |f3| %>
<%= f3.text_field :name %>
<% end %>
<% end %>
<% end %>
Here are my models:
class Project < ActiveRecord::Base
has_many :project_applications
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true, :reject_if => proc { |a| a[:content].blank? }
end
class Question < ActiveRecord::Base
belongs_to :project
has_many :answers
has_many :project_applications, through: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :project_application
end
class ProjectApplication < ActiveRecord::Base
belongs_to :project
belongs_to :student
has_many :answers
has_many :questions, through: :answers
end
A project is created by an employer, and a student can create a project_application. The project_application should present the questions and then show form fields that correspond to the questions answers. I cannot for the life of me figure out how the form view should look. I need a form_for ProjectApplication that accepts nested attributes for answers. I have the following in my controller:
class ProjectApplicationsController < ApplicationController
def new
#project = Project.find(params[:project_id])
#project_application = ProjectApplication.new
#project_application.project = #project
#project_application.project.questions.each do |question|
#answer = question.answers.build
#answer.project_application = #project_application #this line does not work
puts 'answer' + #answer.inspect.to_s
end
puts 'here are the answers' + #project_application.answers.inspect.to_s
end
end
The problem with this is that the answers are not correctly being associated with project_applications because the project_applications don't have an id yet (because they have not been created) so the association can't happen, so the answer fields are not displayed. Here is the view code (does not work) that I have now:
<%= form_for #project_application, url: project_project_applications_path(#project.id), method: :post, remote: true do |f| %>
<%= f.fields_for :project do |proj| %>
<%= proj.fields_for :questions do |quest| %>
<%= quest.fields_for :answers do |answer| %>
<%= answer.text_area :content %>
<% end %>
<% end %>
<% end %>
<%= f.submit "APPLY" %>
<% end %>
How do I change the view and/or controller to properly display answer fields correctly associated with questions and the project application?
My understanding:
You have projects
Each project has many questions & project_applications
Each question belongs to a project, has many answers through project_applications
For each project, you'd like to create applications with the applicable answers. Here's what I'd do:
Models
class Project < ActiveRecord::Base
has_many :project_applications
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :project
has_many :answers
has_many :project_applications, through: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :project_application
end
class ProjectApplication < ActiveRecord::Base
belongs_to :project
belongs_to :student
has_many :answers
has_many :questions, through: :project
accepts_nested_attributes_for :answers
def self.build
application = self.new
application.answers.build
end
end
Controller
#app/controllers/project_applications_controller.rb
def new
#projectapplication = ProjectApplication.build
#project = #projectapplication.project
end
def create
end
private
def application_params
params.require(:project_application).permit(:application, :attributes, :here, answer_attributes: [:content])
end
Views
#app/views/project_applications/new.html.erb
<%= form_for #projectapplication do |f| %>
<%= f.text_field :application_fields %>
<% #project.questions.each do |question| %>
<%= f.fields_for :answers, question do |answer| %>
<%= answer.hidden_field :question_id, question.id %>
<%= answer.text_field :content, placeholder: question.content %>
<% end %>
<% end %>
<% end %>
Process
This works by creating a new project_application, sending answers directly to the answer model. Because answers are directly associated with questions, and projectsthrough questions, you should be able to get it working without passing any more data
This might not work outright, but I'm sure with some tweaking it will deliver the desired result