I'm trying to build a nested form, but the nested form of question_fields isn't rendering in the browser. That form has a nested form called answers, also not rendering
Here's the nested form, _createpoll.html.haml
= form_for Poll.new, :class=>'create-poll-form', :remote => true do |f|
= f.text_field :title, :autofocus => true, :placeholder => "Poll Title"
= f.text_field :description, :placeholder => 'Description'
/ Required accepts_nested_attributes_for :questions in the polls model.
= f.fields_for :questions do |builder|
= render "questions/question_fields", :f => builder
= f.submit "Create Poll", :class => 'btn btn-danger'
Here's the _questions_fields.html.haml:
%p
= f.label :content, "Question"
= f.text_area :content
= f.check_box :_destroy
= f.label :_destroy, "Remove Question"
%p
= f.fields_for :answers do |builder|
= render "answers/answer_fields", :f => builder
Here's the related Polls Controller, new and create actions
def create
#poll = Poll.create(params[:poll])
end
def new
#poll = Poll.new
1.times do
question = #poll.questions.build
2.times {question.answers.build}
end
end
Any ideas on why this might not be rendering? Thanks in advance for the tips!!
Update, a new question
After creating the poll with its associated questions and answers, after querying the database, I see that that the foreign keys aren't persisted and the association is lost. Do I have to use hidden fields here somehow?
Silly oversight. Poll.new has to be #poll... whoops.
Updating Answer.
This form was was rendered by a button on the user's dashboard, "Create Poll," routing through the controller's new action.
As controller's new action instantiates the new poll, I was being redundant when instantiating another poll in the form for. By switching this to the newly created instance variable #poll, the form rendered. Also, :content threw a no method error, but that's because it wasn't in the attr_accessible of the questions or answers models.
Related
I have 2 tables, landslides and sources (maybe doesn't relate to each other). I want a form which lets user to fill in information and then submit to both tables. Here's my current form without sources fields:
= form_for :landslide, :url => {:controller => 'landslides', :action => 'create'} do |f|
.form-inputs
%form#landslideForm
.form-group.row
%label.col-sm-2.col-form-label{for: "textinput"}Date
.col-sm-10
= f.date_select :start_date, :class => "form-control"
#Some fields
.form-actions
= f.button :submit, class: "btn btn-lg btn-primary col-sm-offset-5", id: "submitButton"
And parameters:
def landslide_params
params.require(:landslide).permit(:start_date, :continent, :country, :location, :landslide_type, :lat, :lng, :mapped, :trigger, :spatial_area, :fatalities, :injuries, :notes)
end
def source_params
params.require(:source).permit(:url, :text, :landslide_id)
end
Also there's a column in sources calls landslide_id which take the landslide ID from table landslides. So when a user submits a new landslide, how can I take the upcoming landslide ID (which is auto increment, user doesn't need to fill in)?
Thanks!
HTML does not allow nested <form> elements and you can't pass the id of record that has not been persisted yet through a form (because it does not have an id).
To create a nested resource in the same request you use accepts_nested_attributes_for:
class Landslide
# or has_many
has_one :source
accepts_nested_attributes_for :source
end
class Source
belongs_to :landslide
end
This means that you can do Landslide.create(source_attributes: { foo: 'bar' }) and it will create both a Landslide and a Source record and will automatically link them through sources.landslide_id.
To create the form inputs use fields_for:
# use convention over configuration
= form_for #landslide do |f|
.form-inputs
.form-group.row
# use the form builder to create labels instead
= f.label :start_date, class: 'col-sm-2 col-form-label'
.col-sm-10
= f.date_select :start_date, class: "form-control"
%fieldset
%legend Source
= f.fields_for :sources do |s|
.form-group.row
= s.label :url, class: 'col-sm-2 col-form-label'
.col-sm-10
= s.text_field :url, class: "form-control"
# ...
class LandslidesController
# ...
def new
#landslide = Landslide.new
# this is needed to seed the form with inputs for source
#landslide.source.new
end
def create
#landslide = Landslide.new(landslide_params)
if #landslide.save
redirect_to #landslide
else
#landslide.source.new unless #landslide.source.any?
render :new
end
end
private
def landslide_params
params.require(:landslide).permit(
:start_date, :continent, :country,
:location, :landslide_type,
:lat, :lng, :mapped, :trigger, :spatial_area,
:fatalities, :injuries, :notes,
source_attributes: [ :url, :text ]
)
end
end
You need to use accept_nested_attributes_for and nest your form accordingly:
(With reservation in regards to what form should be nested in which, I use the example of Sources submitted via landslide-form.)
in landslide.rb
accept_nested_attributes_for :sources
In your view (I don't know haml but anyways)
<%= form_for :landslide do |f|%>
<%= f.select :start_date %>
<%= fields_for :sources do |s| %>
<%= s.input :your_column %>
<% end %>
<%= f.button :submit %>
<% end %>
Btw, there are a lot of questions on this already, it's called 'Nested Forms'
Nested forms in rails - accessing attribute in has_many relation
Rails -- fields_for not working?
fields_for in rails view
I try to create a new message for a conversation via a form.
class Conversation < ActiveRecord::Base
has_many :messages
accepts_nested_attributes_for :messages
So far I'm using a partial to create new messages _new_message.html.erb:
<div id="message_dialog">
<%= simple_form_for [:front, #conversation] do |f| %>
<%= f.input :challenge_id, as: :hidden, input_html: { value: #conversation.challenge.id } %>
<%= f.simple_fields_for :messages do |m| %>
<%= m.input :subject %>
<%= m.input :text, as: :text %>
<%= m.input :recipient_id, as: :hidden, input_html: { value: #conversation.challenge.user_id } %>
<%= m.input :sender_id, as: :hidden, input_html: { value: current_user.id } %>
<% end %>
<%= f.button :submit %>
<% end %>
</div>
In the ConversationsController's show action I define:
#conversation = Conversation.find(params[:id])
#conversation.messages.build
This works for creating a new message for a new conversation.
However, when I try to create a new message for a conversation which already has other messages the form helper creates an edit field for every message which belongs to the conversation
What is the best way to create a new nested ressource?
Preferrably, I'd like to use the same partial to create a new message for a new conversation and a new message for an existing conversation which already has other messages.
After a couple of hours I found the answer.
simple_fields_for can have either one or two arguments.
With one argument
<%= f.simple_fields_for :messages do |g| %>
the form helper creates input fields for all the messages of the conversation.
With two arguments
<%= f.simple_fields_for :messages, #conversation.messages.build do |f| %>
the form helper creates input fields only for the new message I want to create. This means we specify the object name and the object itself independently here according to this source.
Ach, and don't forget to whitelist the the nested params:
def conversation_params
params.require(:conversation).permit(..., messages_params: [:subject, ...])
end
This question already has an answer here:
Rails -- how to populate parent object id using nested attributes for child object and strong parameters?
(1 answer)
Closed 6 years ago.
I'm trying to build a nested form that will create a new user and subscribe them to a plan.
When I hit "Enroll" I get the following error:
Validation failed: Plan subscriptions user can't be blank
I've double and triple checked everything below and am not sure what's wrong at this point. Any idea why the subscription is not being associated to the new user record?
Here's my code:
User.rb
class User < ActiveRecord::Base
attr_accessible ..., :plan_subscriptions_attributes
has_many :plan_subscriptions, dependent: :destroy
accepts_nested_attributes_for :plan_subscriptions
PlanSubscriptions.rb
class PlanSubscription < ActiveRecord::Base
belongs_to :user
Plan_Subscriptions#new
def new
#plan = Plan.find(params[:plan_id])
#user = User.new
#user.plan_subscriptions.build
end
Plan_Subscriptions/New.html
<%= form_for #user do |f| %>
<fieldset>
<%= f.text_field :first_name, :label => false, :placeholder => 'First Name', :required => false %>
<%= f.text_field :last_name, :label => false, :placeholder => 'Last Name',
<%= f.fields_for :plan_subscriptions do |builder| %>
<%= builder.hidden_field :plan_id, :value => #plan.id %>
<% end %>
<%= f.submit 'Enroll', :error => false %>
</fieldset>
<% end %>
Plan wont have an id at this point in the execution so I wouldn't use that. Try removing the line:
<%= builder.hidden_field :plan_id, :value => #plan.id %>
#plan.id will be nil, so it will overwrite the automatically built object and thus fail the validation.
Then attempt to submit the form again. Try adding a valid form element for the plan subscription if you want the user to set something in the subscription.
I'm building my first Rails Application and until now everything went fine but then I found the following scenario: One Presentation is supposed to have N Iterations. I'm NOT using REST. So, I was trying to make a simple form to create iterations.
These are the models:
class Presentation < ActiveRecord::Base
has_many :iterations
end
class Iteration < ActiveRecord::Base
belongs_to :presentation
attr_accessible :presentation_id, :description, :delivery_date, :file
validates :presentation_id, :presence => {:message => 'is required.'}
end
These are the actions in the controller:
#Shows Form
def add
#iteration = Iteration.new
#presentation = Presentation.find(params[:id])
end
#Saves Form
def save
#iteration = Iteration.new(params[:iteration])
#iteration.delivery_date = Time.now
if #iteration.save
flash[:notice] = "Saved succesfully!"
else
flash[:error] = "Changes were not saved."
end
redirect_to root_url
end
These would be the view in HAML:
= form_for #iteration, :url => { :action => "save", :method => "post" }, :html => { :multipart => true } do |f|
- if #iteration.errors.any?
There were some errors:
.notice-text.fg-color-white
%ul.notice
- for message in #iteration.errors.full_messages
%li= message
%br
.field
= f.label :description, "Description"
= f.text_area :description, :class=>"form-text-area", :rows=>5
.field
= f.label :file, "Upload File"
= f.file_field :file
.field
= hidden_field_tag :presentation_id, #presentation.id
%br
= f.submit "Save"
The problem is, save method wont save, but #iteration.errors.count's value on the view is 0.
I used then save! instead as I read in another post, that way it throw the following error:
Validation failed: Presentation is required.
I can't figure out what I'm doing wrong. Please notice that in the view I used to have "f.hidden_field" instead of "hidden_field_tag" but I changed it for some other reasons, however I was getting the same error before that.
Your HAML,
hidden_field_tag :presentation_id
needs to be,
f.hidden_field :presentation_id, :value => #presentation.id
Looking at the your model definition you can have,
Nested resource: Refer to Controller path for nested resource - undefined method `<controller>_path'
Use Virtual attributes: Extremely useful railcasts by Ryan on this -> http://railscasts.com/episodes/16-virtual-attributes-revised
Save the presentation id in session: (This is not a clean very clean method)
On your controller, you will need to instantiate iteration on presentation so that presentation id is correctly populated.
def create
#addpost = Post.new params[:data]
if #addpost.save
flash[:notice] = "Post has been saved successfully."
redirect_to posts_path
else
flash[:notice] = "Post can not be saved, please enter information."
end
end
If the post is not saved then it redirects to http://0.0.0.0:3000/posts , but i need to stay on the page, with text input fields so that user can input data.
post model
class Post < ActiveRecord::Base
has_many :comments
validates :title, :presence => true
validates :content, :presence => true
validates :category_id, :presence => true
validates :tags, :presence => true
end
new method
def new
#arr_select = { 1=>"One",2=>"Two" ,3=>"Three" }
#categories_select = Category.all.collect {|c| [ c.category_name, c.id ] }
end
new.html.erb
<h3>Add post</h3>
<%= form_tag :controller=>'posts', :action=>'create' do %>
<%= label :q, :Title %>
<%= text_field :data, :title, :class => :addtextsize %><br/>
<%= label :q, :Content %>
<%= text_area :data, :content, :rows=>10 , :class => :addtextarea %><br/>
<%= label :q, :Category %>
<%= select :data, :category_id, #categories_select %><br/>
<%= label :q, :Tags %>
<%= text_field :data, :tags, :class => :addtextsize %><br/>
<%= label :q, :Submit %>
<%= submit_tag "Add Post" %>
<% end %>
What should i do ?
flash.now with render is what you're looking for.
flash.now[:notice] = "Post can not be saved, please enter information."
render :new
Also instead of
flash[:notice] = "Post has been saved successfully."
redirect_to posts_path
you can just write
redirect_to posts_path, :notice => "Post has been saved successfully."
and it will do the same thing. It works only with redirect_to though, not with render!
Something like this should do what you want:
flash[:notice] = "Post can not be saved, please enter information."
render :new
UPDATE: You updated your question so I have to update my answer. Render is the right way to do this. However, it looks like you load some categories and some other collection of stuff in your new method. Those same instance variables should be available to your create method. The cleanest way to do this is put them into another method and have that method used as a before_filter applied to both create and new. Something like this:
before_filter :load_stuff, :only => [:create, :new]
def load_stuff
#arr_select = { 1=>"One",2=>"Two" ,3=>"Three" }
#categories_select = Category.all.collect {|c| [ c.category_name, c.id ] }
end
Then your new method is pretty much blank and calling render :new in your create method should work.
Hey this answer is super late but thought I'd add it for anyone that comes across it. Probably the most simple solution for what you want to achieve is to add required: true to all of the form inputs you want filled out. E.g
f.text_field :title, required: true, class: "whateverclassyouwant"
This way the form will ONLY be submitted if these fields have been filled in correctly and if not an error flash message will pop up on the field that it needs to be completed. The default flash messages that pop up can be custom styled also, Google how to do so.
This way you can remove the else redirect all together in your create method as it will never get to that point, and just have the if save, flash success etc.