There have been many questions about this, but none of them seem to help. And yes, I have watched this rails cast.
I have an Author who has many Books, like so:
Author:
class Author < ActiveRecord::Base
attr_accessible :name
has_many :books, dependent: :destroy
accepts_nested_attributes_for :books, allow_destroy: true
validates :name, presence: true
validates :name, length: { minimum: 3 }
end
Book:
class Book < ActiveRecord::Base
attr_accessible :name, :year
belongs_to :author
validates :name, :year, presence: true
validates :year, numericality: { only_integer: true, less_than_or_equal_to: Time.now.year }
end
I created the following form to add a book to an author in authors#show:
<%= form_for([#author, #book], html: { class: "well" }) do |f| %>
<% if #book.errors.any? %>
<div class="alert alert-block">
<ul>
<% #author.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#labels and buttons...
<% end %>
...with the following authors_controller method:
def show
#author = Author.find(params[:id])
#book = #author.books.build
end
...and the following books_controller method:
def create
#author = Author.find(params[:author_id])
if #author.books.create(params[:book])
redirect_to author_path(#author)
else
render action: :show
end
end
I cannot seem to figure out why the form does not display any error messages. I followed the example from railscasts where they say there should be an instance variable of books in the form instead of #author.books.build, so I put the latter in the controller and #book in the form - still to no avail.
Thanks for any help!
Let's step through it.
You submit the create, and that enters your create action
def create
#author = Author.find(params[:author_id])
if #author.books.create(params[:book])
redirect_to author_path(#author)
else
render action: :show
end
end
(Side note, what if #author is not found. You are not handling that case.)
Now, the Author is found, but #author.books.create fails (returns false), so you render the show action.
This uses the show template, but does not call the show action code. (Side note, maybe the new page would be a better choice, so the user can try to create again.)
At this point #author is instantiated with the Author you found, but not #book. So #book, if called will be nil.
Your show template does
if #book.errors.any?
which will not be true, so the rest of the template inside the if will be skipped. That's why there are no errors.
You don't need a form_for to display error messages. If you switch to using the new template, then there will be a form to try again.
So let's switch to rendering new.
Class BooksController < ApplicationController
def new
#author = Author.find(params[:author_id])
#book = #author.books.build
end
def create
#author = Author.find(params[:author_id])
#book = #author.books.build(params[:book])
if #author.save
redirect_to author_path(#author)
else
render action: :new
end
end
Your new template will be
<% if #author.errors.any? %>
<div class="alert alert-block">
<ul>
<% #author.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% if #book.errors.any? %>
<div class="alert alert-block">
<ul>
<% #book.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for([#author, #book], html: { class: "well" }) do |f| %>
#labels and buttons...
<% end %>
In Books controller
/books_controller.rb
def new
#author = Author.find_by_id(params[:author_id])
#book = #author.books.build
end
def create
#author = Author.find_by_id(params[:author_id])
if #author
#book = #author.books.build(params[:book])
if #book.save
flash[:notice] = "Book saved successfully"
redirect_to author_path(#author)
else
render :new
end
else
flash[:notice] = "Sorry no author found"
redirect_to author_path
end
end
If author is not present redirect to authors index page with error message dont render the new form as you'll not be able to build the books form as author is nil.
And in your books new form you can have the error listed for books
/books/new.html.erb
<% if #book.errors.any? %>
<div class="alert alert-block">
<ul>
<% #books.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Related
I am trying to create a basic survey tool as my first Rails project.
At the moment I am working on the validation for submitting a new answer to a survey question. The following is my answer model.
class Answer < ApplicationRecord
belongs_to :question
belongs_to :participant
validates :text, presence: true,
length: { minimum: 5, maximum: 100 }
end
I have set up an if statement that takes you to the next question if passes validation. My problem is I'm not sure what to render inside the else for this statement.
For similar validation in other controllers I have written the render statement to be the pages URL. For example: View all questions + add new question are rendered on the studies/id page. So if question validation fails then render will be 'studies/show'.
The URL to add a new answer looks like this.
http://localhost:3000/studies/20/questions/47/answers/new
For more context here is some of my code:
*Answers Controller*
class AnswersController < ApplicationController
def new
#study = Study.find(params[:study_id])
#question = #study.questions.find(params[:question_id])
#participant = find_participant
#answer = #question.answers.build(participant: #participant)
end
def create
#study = Study.find(params[:study_id])
#question = #study.questions.find(params[:question_id])
#answer = #question.answers.build(answer_params)
if #answer.save
next_question = #question.next_question
redirect_to next_question_path(next_question, #answer) if next_question.present?
else
#I want to render the current page the participant is on to display errors here.
end
end
***some private functions here***
end
* New Answer View *
<div class="wrap">
<h1 class="med-header"><%= #question.question %></h1>
<%= form_with model: #answer, url: study_question_answers_path(#study, #question), local: true do |form| %>
<%= form.hidden_field :participant_id %>
<% #question.answers.each do |answer| %>
<% if answer.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(answer.errors.count, "error") %> prohibited
this answer from being saved:
</h2>
<ul>
<% answer.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
<%= form.text_area :text %><br>
<%= form.submit %>
<% end %>
</div>
What do I render in the else?
create generates the object and saves. new only generates the object.
If #answer.save fails in this situation,
we need to show the form back to the user. format.html { render :new }
If use JSON you need show errors format.json { render json: #anwser.errors, status: :unprocessable_entity }
Reference : How CURD Work
The cheap way of doing this:
if #answer.save
next_question = #question.next_question
redirect_to next_question_path(next_question, #answer) if next_question.present?
else
flash[:notice] = #answer.errors.full_messages.first
render :new
end
render :new will render the new view as you can infer.
Anecdotally, your next_question_path(next_question, #answer) if next_question.present? is an unlocked gate if there's no next_question :)
The example code below is a contrived example of an attempt at a form object where it is probably overkill to utilize a form object. Nonetheless: it shows the issue I am having:
I have two models: a User and an Email:
# app/models/user.rb
class User < ApplicationRecord
has_many :emails
end
# app/models/user.rb
class Email < ApplicationRecord
belongs_to :user
end
I want to create a form object which creates a user record, and then creates three associated email records.
Here are my form object classes:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
attr_accessor :name, :email_forms
validates :name, presence: true
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
puts "The Form is VALID!"
puts "I would proceed to create all the necessary objects by hand"
user = User.create(name: name)
email_forms.each do |email|
Email.create(user: user, email_text: email.email_text)
end
end
end
# app/forms/email_form.rb
class EmailForm
include ActiveModel::Model
attr_accessor :email_text, :user_id
validates :email_text, presence: true
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
puts "The Form is VALID!"
# DON'T THINK I WOULD PERSIST DATA HERE
# INSTEAD DO IT IN THE user_form
end
end
Notice: the validations on the form objects. A user_form is considered to be invalid if it's name attribute is blank, or if the email_text attribute is left blank for any of the email_form objects inside it's email_forms array.
For brevity: I will just be going through the new and create action of utilizing the user_form:
# app/controllers/user_controller.rb
class UsersController < ApplicationController
def new
#user_form = UserForm.new
#user_form.email_forms = [EmailForm.new, EmailForm.new, EmailForm.new]
end
def create
#user_form = UserForm.new(user_form_params)
if #user_form.save
redirect_to users_path, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {email_forms: [:_destroy, :id, :email_text, :user_id]})
end
end
Lastly: the form itself:
# app/views/users/new.html.erb
<h1>New User</h1>
<%= render 'form', user_form: #user_form %>
<%= link_to 'Back', users_path %>
# app/views/users/_form.html.erb
<%= form_for(user_form, url: users_path) do |f| %>
<% if user_form.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user_form.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% user_form.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
# MESSY, but couldn't think of a better way to do this...
<% unique_index = 0 %>
<% user_form.email_forms.each do |email_form| %>
<div class="field">
<%= label_tag "user_form[email_forms][#{unique_index}][email_text]", "Email Text" %>
<%= text_field_tag "user_form[email_forms][#{unique_index}][email_text]" %>
</div>
<% unique_index += 1 %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The form does render:
And here is the form's html:
I go to submit the form. Here is the params hash:
Parameters: {"utf8"=>"✓", "authenticity_token"=>”abc123==", "user_form"=>{"name"=>"neil", "email_forms"=>{"0"=>{"email_text"=>"test_email_1"}, "1"=>{"email_text"=>"test_email_2"}, "2"=>{"email_text"=>""}}}, "commit"=>"Create User form"}
What should happen is the form should be re-rendered and nothing persisted because the form_object is invalid: All three associated emails must NOT be blank. However: the form_object thinks it is valid, and it blows up in the persist! method on the UserForm. It highlights the Email.create(user: user, email_text: email.email_text) line and says:
undefined method `email_text' for ["0", {"email_text"=>"test_email_1"}]:Array
Clearly there are a couple things going on: The nested validations appear to not be working, and I am having trouble rebuilding each of the emails from the params hash.
Resources I have already examined:
This Article seemed promising but I was having trouble getting it to work.
I have attempted an implementation with the virtus gem and the reform-rails gem. I have pending questions posted for both of those implementations as well: virtus attempt here and then reform-rails attempt here.
I have attempted plugging in accepts_nested_attributes, but was having trouble figuring out how to utilize that with a form object, as well as a nested form object (like in this code example). Part of the issue was that has_many and accepts_nested_attributes_for do not appear to be included in ActiveModel::Model.
Any guidance on getting this form object to do what is expected would be very much appreciated! Thanks!
Complete Answer
Models:
#app/models/user.rb
class User < ApplicationRecord
has_many :emails
end
#app/models/email.rb
class Email < ApplicationRecord
belongs_to :user
end
Controller:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user_form = UserForm.new
#user_form.emails = [EmailForm.new, EmailForm.new, EmailForm.new]
end
def create
#user_form = UserForm.new(user_form_params)
if #user_form.save
redirect_to users_path, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {emails_attributes: [:email_text]})
end
end
Form Objects:
#app/forms/user_form.rb
class UserForm
include ActiveModel::Model
attr_accessor :name, :emails
validates :name, presence: true
validate :all_emails_valid
def emails_attributes=(attributes)
#emails ||= []
attributes.each do |_int, email_params|
email = EmailForm.new(email_params)
#emails.push(email)
end
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
user = User.new(name: name)
new_emails = emails.map do |email_form|
Email.new(email_text: email_form.email_text)
end
user.emails = new_emails
user.save!
end
def all_emails_valid
emails.each do |email_form|
errors.add(:base, "Email Must Be Present") unless email_form.valid?
end
throw(:abort) if errors.any?
end
end
app/forms/email_form.rb
class EmailForm
include ActiveModel::Model
attr_accessor :email_text, :user_id
validates :email_text, presence: true
end
Views:
app/views/users/new.html.erb
<h1>New User</h1>
<%= render 'form', user_form: #user_form %>
<%= link_to 'Back', users_path %>
#app/views/users/_form.html.erb
<%= form_for(user_form, url: users_path) do |f| %>
<% if user_form.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user_form.errors.count, "error") %> prohibited this User from being saved:</h2>
<ul>
<% user_form.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= f.fields_for :emails do |email_form| %>
<div class="field">
<%= email_form.label :email_text %>
<%= email_form.text_field :email_text %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I have a nested relationship where dashboard has many rewards, and I am trying to add a fields_for to the page in order to edit the rewards. Unfortunately, it doesn't seem to be working and I don't know why.
Here's what I have.
Dashboard model:
class Dashboard < ActiveRecord::Base
belongs_to :manager
has_many :rewards
accepts_nested_attributes_for :rewards, allow_destroy: true
end
Rewards model:
class Reward < ActiveRecord::Base
belongs_to :dashboard
end
Dashboard controller:
class DashboardsController < ApplicationController
before_action :authenticate_manager!
# Requires user to be signed in
def index
#dashboards = Dashboard.all
end
def new
#dashboard = Dashboard.new
end
def edit
#dashboard = Dashboard.find(params[:id])
end
def create
#dashboard = Dashboard.new(dashboard_params)
#dashboard.save
if #dashboard.save
redirect_to dashboard_path(#dashboard)
else
render :action => new
end
end
def update
#dashboard = Dashboard.find(params[:id])
if #dashboard.update(dashboard_params)
redirect_to :action => :show
else
render 'edit'
end
end
def show
#dashboard = Dashboard.find(params[:id])
end
def destroy
#dashboard = Dashboard.find_by_id(params[:id])
if #dashboard.destroy
redirect_to dashboards_path
end
end
private
def dashboard_params
args = params.require(:dashboard).permit(:title, :description, :rewards, {rewards_attributes: [ :id, :title, :referralAmount, :dashboardid, :selected, :_destroy] } )
args
end
end
Form in dashboards view:
<%= form_for :dashboard, url: dashboard_path(#dashboard), method: :patch do |f| %>
<% if #dashboard.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#dashboard.errors.count, "error") %> prohibited
this dashboard from being saved:
</h2>
<ul>
<% #dashboard.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_field :description %>
</p>
<%= f.fields_for :rewards do |reward| %>
<%= reward.label :title %><br>
<%= reward.text_field :title %>
<%= reward.check_box :_destroy %>
<%= reward.label :_destroy, "Remove reward" %>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
I went ahead and manually added rewards to the database through the rails console and it worked beautifully, but they are not showing up on the page. They will show up if I iterate through them like so
<% if #dashboard.rewards.any? %>
<ul>
<% #dashboard.rewards.each do |reward| %>
<li><%= reward.title %></li>
<li><%= reward.referralAmount %></li>
<% end %>
</ul>
<% else %>
<p>no rewards</p>
<% end %>
However the fields_for does not display the rewards or their content and resultingly allow one to edit them.
Let me know if you need further information/code.
Try to modify your:
View:
<% if #dashboard.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#dashboard.errors.count, "error") %> prohibited
this dashboard from being saved:
</h2>
<ul>
<% #dashboard.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for #dashboard, url: dashboard_path(#dashboard) do |f| %>
........
<% end %>
Controller (has_many relationship):
def new
#dashboard = Dashboard.new
#dashboard.rewards.build
end
private
def dashboard_params
params.require(:dashboard).permit(:title, :description,
rewards_attributes: [
:id,
:title,
:referralAmount,
:dashboardid,
:selected,
:_destroy
])
end
You don't have to set the method: patch if form.
Once you got in edit page, Rails will use the update action in controller when form submission.
To check it, run rake routes,
you will see somsthing like this:
PATCH /dashboards/:id(.:format) dashboards#update
PUT /dashboards/:id(.:format) dashboards#update
In controller you need to give build
def new
#dashboard = Dashboard.new
#dashboard.rewards.build
end
"build" is just create a new object in memory so that the view can take this object and display something, especially for a form.
Hope it helps for you
You should build object before nested form. You can add whatever you want that object.
Try it in controller;
def new
#dashboard = Dashboard.new
3.times do
#dashboard.build_reward
end
end
Try setting an "#rewards" instance variable in your dashboards edit method (where #rewards = #dashboard.rewards). Then replace :rewards with #rewards.
Edit:
I believe my initial answer is inapproriate for your exact question (while it would be helpful on say the page to show a specific dashboard and its rewards). The answers above are on the right track re:
refining your params method per #aldrien.h;
Adding #santosh dadi's suggestion of
#dashboard.rewards.build
(assuming you only want one rewards fields on a form for "new")
Finally though, to avoid making fake information for a new rewards form, adding to the top of your Dashboards model:
accepts_nested_attributes_for :rewards, reject_if: lambda {|attributes| attributes['title'].blank?}
http://guides.rubyonrails.org/form_helpers.html#nested-forms
I want to display the validation errors of a new #comment on the articles\view.html.erb when it fails to persist a comment through the comments#create.
TLDR, I have created a blog using the Getting Started tutorial with rails 4.2.6 and ruby 2.2.3.
A blog has many Articles and each Article has many Comments.
Following the tutorial, the form for posting a new comment is in the articles\view.html.erb and it's persisted in the comments#create. After posting a new comment it redirects to the articles#show again.
class Article < ActiveRecord::Base
has_many :comments
validates :title, presence: true, length: { minimum: 5 }
end
class Comment < ActiveRecord::Base
belongs_to :article
end
I use the same view - articles/show.html.erb - to display an article, its comments and post a new comment
<p><strong>Title:</strong><%= #article.title %></
<p><strong>Text:</strong><%= #article.text %></p>
<h2>Comments</h2>
<% #article.comments.each do |comment| %>
<p><strong>Commenter:</strong><%= comment.commenter %></p>
<p><strong>Comment:</strong><%= comment.body %></p>
<% end %>
<h2>Add a comment:</h2>
<%= form_for([#article, #article.comments.build]) do |f| %>
<p><%= f.label :commenter %><br><%= f.text_field :commenter %></p>
<p><%= f.label :body %><br><%= f.text_area :body %></p>
<p><%= f.submit %></p>
<% end %>
<%= link_to 'Edit', edit_article_path(#article) %> |
<%= link_to 'Back', articles_path %>
through the articles_controller
def show
#article = Article.find(params[:id])
end
and the comments_controller to post a new comment
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comment_params)
redirect_to article_path(#article)
end
It worked as expected :)
Here is where my problems started...
I decided to go further by adding some validation to the Comment model
class Comment < ActiveRecord::Base
belongs_to :article
validates :commenter, presence: true
validates :body, presence: true
end
and display the comment errors when it fails to save it.
First, I tried to display the errors trough the article model
<% if #article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#article.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul><% #article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %></ul>
</div>
<% end %>
Then I tried with a global var #comment in articles_controller
def show
#article = Article.find(params[:id])
#comment = #article.comments.build
end
<% if #comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#comment.errors.count, "error") %> prohibited
this comment from being saved:</h2>
<ul><% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %></ul>
</div>
<% end %>
It didn't work too.
As far as I understand it doesn't work that way since when I submit a new comment I redirect to the article show action redirect_to article_path(#article) - either if it succeeded or not to save the new comment - thus a new comment instance is created, overriding the previous one with its errors.
My best solution was using the flash property with the error messages. It looks like this
comments_controller
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comments_params)
redirect_to article_path(#article), flash: {new_article_errors: #comments.errors.full_messages}
end
articles/show.html.erb
<% unless flash[:new_comment_errors].nil? %>
<div id="error_explanation">
<h2>
<%= pluralize(flash[:new_comment_errors].count, "error") %> prohibited this comment from begin saved:
</h2>
<ul>
<% flash[:new_comment_errors].each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
But this isn't the best solution too. It displays the errors but loses the values already filled (the form is clear).
Should I need to pass the #comment through the redirect_to? Something like this
comments_controller
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(comments_params)
if #comment.save
redirect_to article_path(#article)
else
redirect_to article_path(:comment => params[:comment] )
end
end
article_controller
def show
#article = Article.find(params[:id])
#comment = #article.comments.build
#comment.valid? if params[:comment]
end
Now I'm getting the No route matches error. Should I create a new action that receives a params[:comment]? or editing the show route to receive it?
I already read the Nested model validation - errors don't show but didn't help because they suggested to create the new action in the comments_controller and I don't want that (the new post form needs to be in the article/show.html.erb).
Nested Model Form Part 1 and Nested Model Form Part 2 didn't help too.
Thank you for your time and help!
Instead of redirecting to the ArticlesController#show method (which as you've discovered will cause all of your instance variables to be lost) you can, instead, render the show view.
if #comment.save
redirect_to article_path(#article)
else
render :template => 'articles/show'
end
You already have #template and #comment instance variables, so they'll be used in the render.
I have three-tier model:
User has_many Asks has_many Outcomes
On the home page, I would like the user to be able to add an Outcome to their Ask when they mark it complete. I'm trying to use a nested form to display the Outcome description in the Ask form which also updates the done flag and done date.
Like other users/questions here on SO, I cannot get a nested form to display on the screen. I've followed instructions from the other questions, but still the nested field is not displaying. Am wondering if someone can spot the issue in the code below?
Ask Model
class Ask < ActiveRecord::Base
attr_accessible :category, :description, :done, :followed_up,
:helper, :public, :date_done, :date_followed_up, :user_id, :outcomes_attributes
belongs_to :user, counter_cache: true
has_many :outcomes
accepts_nested_attributes_for :outcomes
end
Ask Controller
class AsksController < ApplicationController
def new
#ask = current_user.asks.build(params[:ask])
#ask.outcomes.build
end
def create
#ask = current_user.asks.build(params[:ask])
if #ask.save!
respond_to do |format|
format.html { redirect_to edit_ask_path(#ask) }
format.js
end
else
flash[:error] = "Something is wrong. The Ask was not saved..."
end
end
def edit
#ask = current_user.asks.find(params[:id])
end
def update
#ask = current_user.asks.find(params[:id])
#ask.outcomes.build
#ask.update_attributes(params[:ask])
respond_to do |format|
format.html { redirect_to edit_ask_path(#ask) }
format.js
end
end
end
Home Page Controller (this form is on the home page)
class StaticPagesController < ApplicationController
def home
if signed_in?
#ask = current_user.asks.build(params[:ask])
#ask.outcomes.build
end
end
Form Partial rendered on the home page
<% if current_user.asks.any? %>
<ul id="ask-list-items">
<% current_user.asks.where(done: false).each do |a| %>
<%= form_for(a) do |f| %>
<li><%= a.description %></li>
<%= f.hidden_field :date_done, value: Date.today %>
<%= f.hidden_field :done, :value=>true %>
<%= f.submit "Mark as done", class: "btn btn-small hidden done_btn", id: "a-#{a.id}-done" %>
<%= f.fields_for :outcomes do |builder| %> # << These fields are not showing up
<%= builder.text_area :description, placeholder: "Describe the outcome...", id: "ask-message" %>
<% end %>
<%= f.submit "Save outcome", class: "btn btn-primary" %>
<% end %>
<% end %>
</ul>
<% end %>
When using symbol in form_for and fields_for Rails tries to use an instance variable with he same name, e.g. #outcomes for :outcomes. So try (for existing outcomes):
<% #outcomes = a.outcomes %>
before the line with f.fields_for :outcomes....
And for new outcomes:
<% #outcomes = a.outcomes.build %>
(the last with contribution to the owner of the question)