I have 3 models: User, Answer and Question.
user.rb
has_many :questions
has_many :answers
question.rb
has_many :answers
belongs_to :user
accept_nested_attributes_for :answers
answer.rb
belongs_to :question
belongs_to :user
In questions/show.html.erb
form_for #question do |f|
f.fields_for :answers, #question.answers.build do |builder|
builder.text_area, :body
end
f.submit
end
Submit calls the questions#update action and, thanks to nested resources, will the new answer be saved in the database. I wonder: how can I save the user_id column for answer, in the database, after the question is submitted? Can I somehow pass current_user.id to answer's user_id column after submitting the form?
You have to pass the user parameter in your controller's create action (also you should build your nested attributes in the controller):
def new
#question = Question.new
#question.answers.build
end
def create
#question = current_user.questions.new(params[:questions])
//The line below is what will save the current user to the answers table
#question.answers.first.user = current_user
....
end
Your view's form should therefore look like:
form_for #question do |f|
f.fields_for :answers do |builder|
builder.text_area, :body
end
f.submit
end
You could do something like this in your controller's update action:
# questions_controller
def update
params[:question][:answers_attributes].each do |answer_attribute|
answer_attribute.merge!(:user_id => current_user.id)
end
if #question.update_attributes(params[:question])
...
end
end
Another more simple solution would be to add the user_id to your form like this:
form_for #question do |f|
f.fields_for :answers, #question.answers.build(:user_id => current_user.id) do |builder|
builder.text_area, :body
builder.hidden_field :user_id # This will hold the the user id of the current_user if the question is new
end
f.submit
end
The problem with this approach is that users would be able to edit the value through a HTML source code inspector (like in chrome), and thereby set the user to someone else. You could of course validate this in some way, but that too would be a bit complex.
'current_user' is a devise helper method. it can be accessed in controllers n views directly.
if i m not wrong, only the new answer should have the current_user.id Old answers should not be updated. u can do this as
f.fields_for :answers, #question.answers.build do |a|
a.hidden_field :user_id, :value => current_user.id
a.submit
Related
I have an enrollment form where a user can enroll to some sort of event.
However, I want to give the posibility for teams to enroll also and I was thinking about a wizard like form.
Basically create 5 records at a time.
The problem is, I'll have a new enrollment creation on each step, so I thought the wicked gem would not do it for this scenario.
Can you give me a few guidelines on how should I approach this?
Maybe just render new after creation if a i.e. team attr is sent from the form?
Maybe use self join?
That's off the top of my head but I know there has to be a clever way to do this.
I'm not sure how your models are structured, but if you have something like:
class Attendee
has_many :enrolments
has_many :events, through: :enrolments
end
class Enrolment
has_many :attendees
belongs_to :event
end
class Event
has_many :enrolments
has_many :attendees, through: :enrolments
accepts_nested_attributes_for :enrolments
end
Then you can do something like:
# controllers/enrolments_controller.rb
class EnrolmentController < ApplicationController
def new
#event = Event.find(params[:event_id])
pax = params[:persons].to_i
pax.times do
#event.enrolments.build
end
end
def create
#event = Event.find(params[:event_id])
#event.enrolments.build(enrolment_params)
#event.save
end
protected
def enrolment_params
# require specific parameters here
params.require(:event).permit(:attendee_attributes => [])
end
end
# views/enrolments/new.html.erb
<%= form_for #event, url: event_enrolments_path(#event) do |f| %>
<%= f.hidden_field :event_id %>
<%= f.fields_for :enrolments do |af| %>
<%= af.select :attendee_id, Attendee.all.collect {|p| [ p.name, p.id ] } %>
<% end %>
<%= f.submit %>
<% end %>
# routes.rb
resources :events do
resources :enrolments
end
That's off the top of my head, but the general idea is that you build the nested fields by running event.enrolments.build based on the number of people passed in the params.
This uses fields_for and accepts_nested_attributes_for. This also makes it really convenient to reuse existing forms by passing in the form context in the partial:
<%= f.fields_for :enrolments do |af| %>
<%= render "enrolments/form", f: af %>
<% end %>
I want a User to be able to answer all questions that are assigned to them, in an Answer model. Now I'm trying to create a form that allows me to loop through the questions a User have assigned to them, and answer them in an Answer model.
In the answer model I save the reply, and the question id. However this requires multiple saves in one form, which I'm unable to do.
Model associations look like this:
User
has_many :answers
has_many :questions, through: :question_participants
Answer
belongs_to :user
belongs_to :question
now I'm trying to create an Answer#new form like this:
<%= form_for #answer do |f| %>
<% #questions.each do |question| %>
<h3><%= question.name %></h3>
<%= f.hidden_field :question_id, value: question.id %>
<%= f.text_field :reply, class: 'form-control' %>
<% end %>
<%= f.submit 'Send inn', class: 'btn btn-success' %>
<% end %>
and thus hoping it will allow me to save multiple columns in one, but that doesn't work. It only saves the last column, no matter what.
My answers controller:
class AnswersController < ApplicationController
def new
#questions = current_user.questions
#answer = current_user.answers.new
end
def create
#questions = current_user.questions
#answer = current_user.answers.new(answer_params)
if #answer.save
redirect_to answers_path
else
render 'new'
end
end
private
def answer_params
params.require(:answer).permit(:reply, :question_id)
end
end
What you're looking for is accepts_nested_attributes_for:
This should work:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :answers
has_many :questions, through: :answers
accepts_nested_attributes_for :answers
#this will have to be populated on user create
before_create :build_answers
private
def build_answers
questions = Question.find [1,3,4,6]
questions.each do |question|
user.build_answer(question: question)
end
end
end
#app/models/answer.rb
class Answer < ActiveRecord::Base
#columns id | user_id | question_id | response | created_at | updated_at
belongs_to :user
belongs_to :question
end
#app/models/question.rb
class Question < ActiveRecord::Base
has_many :answers
has_many :users, through: :answers
end
This will give you the ability to do the following:
#config/routes.rb
resources :answers, only: [:edit, :update]
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def edit
#questions = current_user.questions
end
def update
#answers = current_user.answers.update answer_params
end
private
def answer_params
params.require(:answer).permit(:response) #-> question_id and user_id set on create, don't need to be changed
end
end
This will allow you to use the following form:
#app/views/answers/edit.html.erb
<%= form_tag answers_update_path, method: :patch do |f| %>
<% #questions.each do |question| %>
<%= f.fields_for "answers[]", question do |qf| %>
<%= qf.label question.title %>
<%= qf.text_field :response %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
--
Typically, you'd use the accepts_nested_attributes_for with the nested model. However, since you just want multiple answer responses, you can use the above.
The bugs in this would likely be in the strong params, or in the form declaration (IE current_user.questions). If you reply with information, I'll write some upates
Ref: Multiple objects in a Rails form
The problem that I have here is that I have a nested form that won't save to the database and I'm suspecting it's because the proper attributes aren't being passed into the form prior to being saved. Right now I'm trying to pass these values through hidden fields but I'm thinking there's probably a more "Railsy" way to do this. This is the form that I have created to do this:
<%= form_for #topic do |f| %>
<%= render "shared/error_messages", object: f.object %>
<%= f.fields_for :subject do |s| %>
<%= s.label :name, "Subject" %>
<%= collection_select :subject, :id, Subject.all, :id, :name, {prompt:"Select a subject"} %>
<% end %>
<%= f.label :name, "Topic" %>
<%= f.text_field :name %>
<div class="text-center"><%= f.submit class: "button radius" %></div>
<% end %>
This form generates a params hash that looks like this:
{"utf8"=>"✓", "authenticity_token"=>"PdxVyZa3X7Sc6mjjQy1at/Ri7NpR4IPUzW09Fs8I710=", "subject"=>{"id"=>"5"}, "topic"=>{"name"=>"Ruby"}, "commit"=>"Create Topic", "action"=>"create", "controller"=>"topics"}
This my model for user.rb:
class User < ActiveRecord::Base
has_many :topics
has_many :subjects, through: :topics
end
In my subject.rb file:
class Subject < ActiveRecord::Base
has_many :topics
has_many :users, through: :topics, dependent: :destroy
validates :name, presence: true
end
In my topic.rb file:
class Topic < ActiveRecord::Base
belongs_to :subject
belongs_to :user
accepts_nested_attributes_for :subject
validates :name, presence: true
end
class TopicsController < ApplicationController
before_filter :require_login
def new
#topic = Topic.new
#topic.build_subject
end
def create
#topic = Topic.new(topic_params)
#topic.user_id = current_user.id
#topic.subject_id = params[:subject][:id]
if #topic.save
flash[:success] = "Success!"
render :new
else
flash[:error] = "Error!"
render :new
end
end
private
def topic_params
params.require(:topic).permit(:name,:subject_id,:user_id, subjects_attributes: [:name])
end
end
So I'm getting closer to having a successful form submission! I placed the method accepts_nested_attributes_for in the join model, which in this case is in topic.rb. I don't really know why this works but I'm thinking it allows Rails to properly set the ":user_id" and ":subject_id" compared to placing accepts_nested_attributes_for on a model containing the "has_many through" relationship. I saw it on this post btw: http://makandracards.com/makandra/1346-popular-mistakes-when-using-nested-forms
NOW, I still have a problem where the ":subject_id" isn't being properly saved into the database. Would I have to pass in a hidden field to do this or would I have to do something else like nest my routes?
Wow that took forever to figure this one out. Since I have a has_many through relationship and I'm trying to created a nested form involving one of these models the problem I was having was I was placing the "accepts_nested_attributes_for" in the wrong model I was placing it in the has_many through model in the subject.rb file when it should have been placed in the model responsible for the join between two tables.
Also I made a super idiotic mistake on this line when I was trying to save the ":subject_id". I was writing this: #topic.subject_id = params[:subject_id][:id] instead of something like this:
#topic.subject_id = params[:subject][:id]
It was a really dumb mistake (probably because I was copying a pasting code from another controller haha)
Anyways I hope others can learn from my mistake if they ever want to do a nested form on models with a "has_many through" relationship, in certain cases the "accepts_nested_attributes_for" method will go on the JOIN table and NOT on the model with the "has_many through" relationship
I have an application where there are a few nested models... Two parent models and two child models.
I'm trying to create comments on the child models and I had it working great for the first one until I realized I have to create comments on the second child so I realized I had to scrap my work because I was targeting the first parent + child model in the comments controller. So I decided to watch Ryan Bates screencast (http://railscasts.com/episodes/154-polymorphic-association) on creating comments that belong to multiple models...unfortunately, it's not working for me and I'm assuming its because I am trying to create comments on the child models. I will show you what I was using before that worked for the one model and I'll show you what im doing now that doesnt work...
here is what i had for the comments controller
def create
#collection = Collection.find(params[:collection_id])
#design = #collection.designs.find(params[:design_id])
#comment = #design.comments.create(comment_params)
#comment.user = current_user
#comment.save
redirect_to collection_design_path(#collection, #design)
end
and here is what it is now after i tried to implement it to work for multiple models
def create
#commentable = find_commentable
#comment = #commentable.comments.build(comment_params)
#comment.user = current_user
#comment.save
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
here are my crazy routes
resources :collections do
member do
post :like
post :unlike
end
resources :designs do
resources :comments
member do
post :like
post :unlike
end
end
end
anyone have any other different ideas for created comments for multiple nested models? Thanks in advance for the help.
EDIT:
Here was the form I was using for the one model
<%= form_for([#collection, #design, #design.comments.build]) do |f| %>
<%= f.text_area :comment %>
<%= f.submit "Comment", :class => "btn" %>
<% end %>
and here is the one i'm using now
<%= form_for([#collection, #design, #commentable, Comment.new]) do |f| %>
<%= f.text_area :comment %>
<%= f.submit "Comment", :class => "btn" %>
<% end %>
Right now when I try to submit the new comment form I get this error
undefined method `comments' for #<Collection:0x0000010150cf88>
which points back to the create method
EDIT 2
Here is my comment model
belongs_to :commentable, :polymorphic => true
belongs_to :user
Here is my design model (which is a child of the collection model)
has_many :comments, :dependent => :destroy, :as => :commentable
belongs_to :user
belongs_to :collection
and my collection model (which has the child model: design)
belongs_to :user
has_many :designs, :dependent => :destroy
and there is more to the models but its not related to the problem.
I have a model Post that has_many :comments. The form that will post the comment will be shown along with the post in posts/show.html.erb. I have a comments_controller that should handle the creation of comments. Searching on google, I found
<%= form_for([#post, Comment.new], :controller => 'comments', :action => 'create') do |f| %>
But this doesn't work. How do I do this ?
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
#...
class Comment < ActiveRecord::Base
belongs_to :post
#...
Then in the form
form_for #post do |f|
f.fields_for :comments do |c|
c.text_field :title
#...
f.submit
this would create the associated object through active record's accepts_nested_attributes_for, which doesn't require a separate comments_controller. you are submitting to the posts controller, which is handling creating the associated object during the update of the post.
with a comments_controller, you could do one of two things:
send item_id as a param to comments_controller#new, grab the item, then build the new comment from it
#post = Post.find(params[:item_id); #comment = #post.comments.build
put the post_id in a hidden field on the form and just create the comment as normal
# in the controller
#comment = Comment.create(params[:comment])
# in the view
form_for #comment do |f|
f.text_field :title
#...
f.hidden_field :post_id, value: #post.id