I am new to rails and new to Stackoverflow so please bear with me. I am sure there are things beyond my current query that are not done correctly either.
I have an application which has matricies which have questions and submissions, and submissions have nested answers (to the questions in the matricies).
I have a form for users to submit submissions which displays all the relevant questions for the particular matrix and allows users to answer them. This all renders and saves correctly in the submissions/answers tables in the database.
However, when I edit a submission it correctly prepopulates the fields with the previously entered data, so that part is working correctly, but when I save it simply duplicates it all.
I am sure I have made some mistake in my relationships declarations as a submission should only be allowed one answer to each question, but I have been going round in circles for hours and could well be looking in the wrong place.
You can see the effect here: http://digital-maturity-matrix.herokuapp.com/matrices/1/submissions/2
My model:
class Submission < ActiveRecord::Base
belongs_to :matrix
belongs_to :user
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers,
:allow_destroy => true
end
My controller:
def edit
#matrix = Matrix.find(params[:matrix_id])
#submission = Submission.find(params[:id])
#answers = #submission.answers
#questions = #matrix.questions
end
def update
#matrix = Matrix.find(params[:matrix_id])
#user_id = current_user
respond_to do |format|
if #submission.update(submission_params)
format.html { redirect_to matrix_submission_path(#matrix,#submission), notice: 'Submission was successfully updated.' }
format.json { render :show, status: :ok, location: #submission }
else
format.html { render :edit }
format.json { render json: #submission.errors, status: :unprocessable_entity }
end
end
end
...
def submission_params
params.require(:submission).permit(:matrix_id, :user_id, :name, :answers_attributes => [:question_answered, :choice, :question_id])
end
My form:
= form_for([#matrix,#submission]) do |f|
- if #submission.errors.any?
#error_explanation
%h2
= pluralize(#submission.errors.count, "error")
prohibited this submission from being saved:
%ul
- #submission.errors.full_messages.each do |message|
%li= message
%br/
.field
= f.hidden_field :matrix_id, :value => params[:matrix_id]
.field
= f.hidden_field :user_id, :value => current_user.id
= f.fields_for :answers, #answers do |answer|
.field
=answer.label answer.object.question_answered
= answer.text_field :choice
.field
= answer.hidden_field :question_answered
.field
= answer.hidden_field :question_id
.actions
= f.submit
Within seconds of posting this I have found my answer. I think the process of writing the question helped.
For anyone else who stumbles across this, I had missed the inclusion of :id in my nested attributes permitted params. I hadn't realised this was required.
So I needed to replace:
def submission_params
params.require(:submission).permit(:matrix_id, :user_id, :name, :answers_attributes => [:question_answered, :choice, :question_id])
end
with:
def submission_params
params.require(:submission).permit(:matrix_id, :user_id, :name, :answers_attributes => [:id, :question_answered, :choice, :question_id])
end
Thanks all,
TB
Related
OK so I've been at this for 6 plus hours and scoured every variation on this question, but nothing I do works!
I'm building a fairly simple family tree app for a friend. There are two models in question, Person and Relationship. Whenever you build a new Person (after the very first one which is handled separately), it should also build a Relationship which is essentially a join table that has a person_1 attribute, a person_2 attribute, and a relationship_type attribute such as "mother".
When I build a new person, that person is saved to the database just fine, but the associated relationship is not. I can see that all of the necessary params are being passed in, and I've read as much as I can about nested params, but no dice.
My code is a big old mess and I'm a beginner so please ignore any unrelated code weirdness.
My Person model (this centers around the out_relationships resource)
class Person < ApplicationRecord
has_many :out_relationships, :class_name => "Relationship", :foreign_key => "person_1_id"
has_many :in_relationships, :class_name => "Relationship", :foreign_key => "person_2_id"
belongs_to :user, optional: true
accepts_nested_attributes_for :out_relationships
end
My Relationship model:
class Relationship < ApplicationRecord
belongs_to :person_1, :class_name => "Person"
belongs_to :person_2, :class_name => "Person"
end
My form:
<%= form_for #person, :url => create_relative_path, html:{method:'post'} do |form| %>
<%= fields_for :out_relationships do |builder| %>
<div class="form-group">
<%= builder.label :relationship_type, "Relationship to #{#root_person.first_name}" %>
<%= builder.text_field :relationship_type, class: "form-control" %>
<%= builder.hidden_field :person_1, value: #person_1.id %>
<%= builder.hidden_field :person_2, value: #person_1.id %>
</div>
<% end %>
I set person_1 and person_2 to the same value just as a test. That's kind of unrelated to the problem, I think, and shouldn't affect what's happening. Anyways...
My controller:
def new
#root_person = Person.find(params[:id])
#person = Person.new
#user = current_user
#root_person.out_relationships.build
end
# GET /people/1/edit
def edit
end
def create
#root_person = Person.find(params[:id])
#person = Person.new(person_params)
#user = current_user
respond_to do |format|
if #person.save
format.html { redirect_to #person, notice: 'Person was successfully created.' }
format.json { render :show, status: :created, location: #person }
else
format.html { render :new }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
def person_params
params.require(:person).permit(:first_name, :middle_name, :last_name, :birth_date, :birth_city,
:birth_country, :current_city, :current_country, :profession, :maiden_name, :marital_status, :patel_type,
out_relationships_attributes: [:person_1, :person_2, :relationship_type])
end
Anyways, that's more of a mess than I even thought, thanks to anyone still reading, would love a nudge in the right direction!
How do I save multiple records with nested attributes using single text_area? Each line in the text box or separated by a comma should be a separate record.
How would the controller look?
_form.html.erb
<%= simple_form_for #project do |f| %>
<%= f.simple_fields_for :products do |g| %>
<%= render 'product_fields', :f => g %>
<% end%>
<%= link_to_add_association 'add item', f, :products %>
<% end %>
_product_fields.html.erb
<%= f.text_field :category, placeholder: "Category" %>
<%= f.text_area :item, placeholder: "List your products (separated by each line or comma)" %>
project_controller.rb
def create
#project = Project.new(project_params)
respond_to do |format|
format.js
if #project.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def project_params
params.require(:project).permit(
:user_id,
products_attributes: [:id, :item, :hyperlink, :_destroy, :category]).merge(user_id: current_user.id)
end
I would like to go into my project form, and then there's a large text_area where I can add a list of products, and each product (separated by "enter" or a "comma") will be a record.
EDIT ----
Adding Models:
class Project < ActiveRecord::Base
has_many :products, dependent: :destroy
accepts_nested_attributes_for :products, :reject_if => :all_blank, allow_destroy: true
end
class Product < ActiveRecord::Base
belongs_to :project
end
Normally you save your params without much manipulation. If you want to turn your text_area param into multiple records just chop it up and process it in the controller.
Let's say you use a new line to delineate products so your text area looks like:
product1
product2
product3
project_params[:product_list] = "product1\nproduct2\nproduct3"
prod_arr = project_params[:product_list].split("\n")
prod_arr.each do |product|
#you now have your product name in the local variable product
#you can now save each one separately. You will probably
#need to take common items of the params hash and insert the
#current product, then save.
end
You can choose to split on just about any character. But pick one that makes sense and then also apply some sort of checking on the string you are splitting. Notice the split("\n") is using double quotes, that is needed to tell Ruby that you are talking about the newline character. If you used split('\n') it won't work.
I would also look at wrapping it in a transaction if you want to make sure they all save.
I am trying to automatically create a child record (Participant) when I create a parent (Project) record. I can create the parent (Project) fine and, on other forms, I can create the child (Participant). I cannot seem to create the child (Participant) at the same time as the parent.
I am on Rails 4, and so I've set my strong params carefully. I just don't understand what I'm doing wrong.
Parent Controller:
class ProjectsController < ApplicationController
def new_project
#title = params[:ti]
#project = Project.new
#project.participants.build
end
def create_project
#project = Project.new(project_params)
#template = Template.find(params[:t])
#project.participants.build
#title = params[:ti]
respond_to do |format|
if #project.save
#project.participants.save
format.html { redirect_to new_milestones_path(:p => #project.id), notice: 'Great! We saved your project details.' }
else
format.html { redirect_to new_project_path(t: #template.id, ti: #title)
}
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def project_params
params.require(:project).permit( :id, :title, :starts, participants_attributes: [:id, :email, :title, :status, :project_id])
end
end
Models:
class Participant < ActiveRecord::Base
belongs_to :project, inverse_of: :participants
........
end
class Project < ActiveRecord::Base
has_many :participants, dependent: :destroy, inverse_of: :project
accepts_nested_attributes_for :participants, allow_destroy: true, reject_if: proc { |a| a["email"].blank? }
.........
end
Form:
<%= form_for #project, url: create_project_path(ti: #title), html: { :multipart => true, :class=> "form-horizontal", id: "basicForm" }do |f| %>
<%= f.fields_for :participants do |ff|%>
<%= ff.hidden_field :email, :value => current_user.email %>
<%= ff.hidden_field :title, :value => 'Organizer' %>
<%= ff.hidden_field :status, :value => 'accepted' %>
<% end %>
<%= f.text_field :title, :placeholder => 'Your Project Title'%>
<%= f.text_field :starts, :placeholder => 'mm/dd/yyyy'%>
<%= f.submit ' SAVE PROJECT' %>
<% end %>
UPDATE:
I added #project.participants.build as Samo suggested (and I've updated my code above), which makes the fields_for visible...but my project doesn't save...it redirects back to new_project_path.
I believe I see the issue. In your new_project action, try this:
def new_project
#title = params[:ti]
#project = Project.new
#project.participants.build
end
To elaborate: fields_for is not going to render anything if the association is blank/empty. You need to have at least one participant returned by #project.participants in order to see its fields. #project.participants.build will simply insert a new instance of the Participant class into the association.
As you're using Rails 4 in your application, you don't need to call accepts_nested_attributes_for, because you are already calling params.requirein your Controller.
After #participant = Participant.new you didn't call Participant.saveaction. You do call a #project.save inside your if condition and you should do that for your #participant too. You can call #project.save before redirecting to project_path. I'm not sure if that is a correct approach, but you can try if it works. :-)
I have a nested form that has 3 fields repeated 7 times. I want to do a check that if the first field group is empty, then it will stop the process and return an error asking a user to fill in those fields.
I already have a check in that it will drop any empty field groups from the Create action in the controller, that is the "reject_if: => :all_blank" part of the model. However, it deletes the first entry, which I would instead rather run a check on. This code doesn't work, and I don't know where to go from here besides trying to refine the check_attendee_form method. Any help would be appreciated out there.
Here are the related Models:
class Registration < ActiveRecord::Base
# Database Relationships
belongs_to :event
belongs_to :user
has_many :attendees
# Form relationships
accepts_nested_attributes_for :attendees,
:allow_destroy => true,
:reject_if => :all_blank
class Attendee < ActiveRecord::Base
# Database relationships
belongs_to :event
belongs_to :user
delegate :event, :event_id, to: :registration
# Validations
validates :first_name, :last_name, presence: true
Controller Create and New Actions and Methods
def new
#registration = #event.registrations.new
7.times { #registration.attendees.build }
end
def create
#registration = #event.registrations.new(registration_params)
#registration.user = current_user
check_attendee_form
respond_to do |format|
if #registration.save
format.html { redirect_to event_path(#event), notice: 'You are now registered.' }
format.json { render :show, status: :created, location: #registration }
else
format.html { render :new }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
end
def check_registration
#check = Registration.find_by event_id: #event, user_id: current_user
if #check.nil?
#registration = #event.registrations.new
#registration.user = current_user
else
redirect_to event_path(#event),
notice: "You're already registered!"
end
end
def check_attendee_form
#attendees_check = #registration.find_by(attendees_attributes: params[:first_name])
if #attendees_check.first.nil?
render :new, notice: "You need to put in your name at least!"
else
end
end
And finally, the essential form info:
<%= simple_form_for [#event, #registration], :html => { :class => 'form-horizontal' } do |f| %>
<%= render 'shared/errors', object: #registration %>
<%= f.simple_fields_for :attendees, defaults: { input_html: { class: 'form-horizontal' } } do |a| %>
<div>
<%= a.label :first_name %>:
<%= a.text_field :first_name %>
<%= error_span([:first_name]) %>
<%= a.label :last_name %>:
<%= a.text_field :last_name %>
<%= error_span([:last_name]) %>
<%= a.label :fundraising_goal %>:
<%= a.number_field :fundraising_goal, placeholder: 'No Commas' %>
<%= error_span([:fundraising_goal]) %>
Here are the params that are getting submitted:
{"utf8"=>"✓",
"authenticity_token"=>"dNR5QCBFplsAG0wzy87+hzaKuG6h2Mlb6xpmKEM0Kko=",
"registration"=>{"attendees_attributes"=>{"0"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"1"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"2"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"3"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"4"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"5"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"6"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""}}},
"commit"=>"Create Registration",
"event_id"=>"5"}
How do I access the first array object with attendees_attributes of ID [0] in this form submission?
If you want to ensure that at least one Attendee is entered for a registration, I believe you could drop the check_attendee_form method and add the following validation to registration.rb:
validate :check_minimum_attendees
def check_minimum_attendees
errors.add(:attendees, 'must be entered') if attendees.size == 0
end
Your form attributes is nested hash, you can access attendees using id like 0, 1 etc
i.e params[registration][attendees][0]
If I am correct, the problem is with :reject_if condition.
You can use a Proc (or lambda) to 'reject' any unsuitable nested attributes.
:reject_if => lambda { |e| e[:last_name].blank? && (e[:_destroy].blank? || e[:_destroy] == 'false') }
Use validation as #keyzee suggested:
validate :check_attendees
def check_attendees
errors.add(:attendees, 'must be entered') unless attendees.count
attendees.each do |a|
errors.add(:attendees, 'error message here') if condition_here
end
end
Or create custom validator in lib folder (lib/check_attendees_validator.rb):
class CheckAttendeesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << 'error_message' if condition_here
end
end
And then use it in a model:
validates :attendees, check_attendees: true
I have a model "Issue" and a nested Model "Relationship"
In the issue.rb I have mentioned:
has_many :relationships, :dependent => :destroy
accepts_nested_attributes_for :relationships, :allow_destroy => true
In relationship.rb I have mentioned:
belongs_to :issue
Following Ryan Bates Railcast#196 I have the following in my issues_controller:
relationship = #issue.relationships.build
However, I am encountering an error "unknown attribute: relationship"
Am I doing something incorrectly here? I do see the Relationships Attributes being passed to the server in the log however, this error does not let the create to be successful.
My expertise with rails is beginners level so kindly excuse me if I am asking a question which maybe deemed trivial.
Thanks for the help.
EDIT: The relevant Controller code:
#relationship = #issue.relationships.build
##relationship = Relationship.new(params[:relationship])
if #relationship.issue_id = ''
#relationship.issue_id = #issueid
end
if #relationship.cause_id = ''
#relationship.cause_id = #issueid
end
#relationship.save
redirect_to(:back, :notice => 'New Relationship was created')
What I see on the trace:
ActiveRecord::UnknownAttributeError in IssuesController#create
unknown attribute: relationship
Among the Issue parameters, I see the Relationship params being passed as expected:
"relationship"=>{"issue_id"=>"100",
"cause_id"=>""}
ANOTHER UPDATE
Posting the form_for code:
- form_for Issue.new do |f|
.field
= f.text_field :description, :class=>"formfield", :id=>"frm_descr"
.field
= f.hidden_field :wiki_url, :class=>"formfield", :id=>"frm_wiki_url"
.field
= f.hidden_field :short_url, :class=>"formfield", :id=>"frm_img_url"
.field
= f.hidden_field :title, :class=>"formfield", :id=>"frm_title"
= f.fields_for :relationship do |builder|
= builder.text_field :issue_id, :class=>"form_field", :id=>"frm_rel_issue_id", :value=>#issue.id
= builder.text_field :cause_id, :class=>"form_field", :id=>"frm_rel_cause_id"
.actions
= f.submit 'Create', :class=>"save_button", :name=>"save_issue_rel_button", :id=>"val_collector"
Change this line
= f.fields_for :relationship do |builder|
to this:
= f.fields_for :relationships do |builder|
Your issue has_many relationships - plural. That will give you the correct relationships_attributes parameters.
Here is the working skeleton code:
I created a new project and tried the combination of the other answers, and finally made it to work.
Here is my solution, after that are the things to watch out for. I am using different models so bear with me:
My models are: discussion has_many posts.
Discussion has no attributes.
Posts has content:text and discussion_id:integer.
Working Code
(model) discussion.rb
has_many :posts
accepts_nested_attributes_for :posts
(model) post.rb
belongs_to :discussion
routes.rb
resources :discussions do
resources :posts
end
(discussion view) _form.html.erb
<%= form_for(#discussion) do |f| %>
<%= f.fields_for :posts, #post do |p| %>
<%= p.text_area :content %>
<% end %>
<%= f.submit %>
<% end %>
(controller) discussions_controller.rb
def new
#discussion = Discussion.new
#post = #discussion.posts.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #discussion }
end
end
def create
#discussion = Discussion.new(params[:discussion])
respond_to do |format|
if #discussion.save
format.html { redirect_to(#discussion, :notice => 'Discussion was successfully created.') }
format.xml { render :xml => #discussion, :status => :created, :location => #discussion }
else
format.html { render :action => "new" }
format.xml { render :xml => #discussion.errors, :status => :unprocessable_entity }
end
end
end
Possible things that can go wrong
First, Thilo was right, I get unknown attribute: post if I do
# WRONG!
f.fields_for :post
Second, I have to have the #post instance variable in new action otherwise the post.context textarea will not show up.
# REQUIRED!
#post = #discussion.posts.build
Third, If I use the f.fields_for #post, the create action will complain unknown attribute: post too.
# WRONG!
f.fields_for #post do |p|
Use this instead:
# RIGHT!
f.fields_for :posts, #post do |p|
The End
So yeah, I wish we get to see more documentations on this (can't see any useful ones). For example I see some use of form_for [#discussion, #post] but I can never get it to work.
By using accepts_nested_attributes, you have created a setter method relationship_attributes=.
There are a couple of things I noticed that need to change.
You don't need to set
#relationship = #issue.relationships.build
Your form should be the following (you have f.fields_for :relationship)
= form_for #issue do |f|
# your issue fields here
= f.fields_for :relationships do |r|
# your relationship fields here
The beauty here is that you won't have to set any ids or anything.
I assume you are constructing the relationship in your controller and then trying to use it in the view. In order for this to be visible, you must make it an instance variable. All you need to do is throw an # symbol in from of the name of relationship, as you have done with #issue.
#relationship = #issue.relationships.build
Edit: due to further information provided by the OP after the original question was asked, this answer is now clearly not applicable.