Rails 3 rendering form with an array of nested attributes after failing validation - ruby-on-rails-3

I have question model which has many options.
In my question controller new action I create five options ready for my user
def new
#question = Question.new
5.times.with_index do |index|
#question.options.build(:order => index)
end
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #question }
end
end
In the view I loop through all options
- form_for(#question) do |f|
.field
= f.label :title, t("question.title")
= show_errors_for(#question, :title)
= f.text_field :title
- #question.options.each do |option|
- f.fields_for :options, option do |o|
.field
= o.label :option, t("question.option_no", { :index => option.order })
= o.text_field :option
= o.hidden_field :order, :value => option.order
.actions
= f.submit t("add_question.create")
My question model looks like this
class Question < ActiveRecord::Base
attr_accessible :title, :options_attributes
belongs_to :user
has_many :options
accepts_nested_attributes_for :options, :reject_if => proc { |attributes| attributes['option'].blank? }
validates :title, :length => { :maximum => 100 }, :presence => true
validate :min_no_of_options
def min_no_of_options
if self.options.size < 3
errors.add_to_base "Must have at least three options"
end
end
end
And my question controller create action
def create
if current_user
#question = current_user.questions.build(params[:question])
else
#question = Question.new(params[:question])
end
if #question.save
redirect_to(#question, :success => t('question.flash_success'))
else
flash.now[:error] = t("question.flash_error")
render :action => "new"
end
end
Now, when I enter only two options in the form and hit the create button the validation prevents the model from being saved. Which is good. But when the create action renders the new action again, only the option fields that I filled are showing up. The three option fields which were left blank have disappeared.
If I replace the "#question.save" in my create action with "false", the behavior is the same. So this suggests that something in the way I create the #question variable in the create action is responsible for throwing away my empty options.
But if I instead remove the :reject_if from my question model the empty options are showing up after a failing question save as expected. (I have a presence validation for the option attribute in my option model) So this tells me that there is nothing wrong in the way I create the #question variable in the create action. It is not throwing away the empty options. So where they are kicked out?
There was one pretty similar question, but the answer in there is not something I would like to do. Though it might be something I have to do.
rails fields_for does not render after validation error on nested form
EDIT
After some more study with rails console I noticed that it truly is the creation of #question variable where the empty options get thrown away. This happens because I have the reject_if defined in the question model. After commenting the reject_if out from the model the empty options were added into the #question variable.
So I guess I need to remove the reject_if and use after_save callback to destroy empty options from the database. This way I will have the empty options going along with the question until the question is saved.

I'm answering my own question because I got the issue solved.
Blank "options" were removed from the "question" because of reject_if in the "question" model. The reject_if statement was applied when the code below was executed, and therefore blank "options" were removed.
#question = current_user.questions.build(params[:question])
I replaced the reject_if with an after_save callback which removes the options which were left blank.

Related

Rails: Create a survey with the data in a database table

I'm currently trying to create a survey which will use questions that are stored in a table. I've read nested model form part 1 from rails casts however i'm not getting anywhere as the questions are not displaying in the survey.
I have three tables, one tables has the text of the questions, another table keeps the record of who entered the survey and a third table which keeps the answers from a user for the questions.
variable table:
name: varchar
id: integer
report table
employee name: varchar
date: date
id: integer
report_variable table
question_id
report_id
answer
Code i modified for reports/new:
# GET /reports/new
# GET /reports/new.json
def new
#report = Report.new
#variable = #report.variable.build #dont know what to do here, gives an error with report_id
respond_to do |format|
format.html # new.html.erb
format.json { render json: #report }
end
end
modified report/_form.html.erb
<div >
<%= f.fields_for :variable do |builder| %>
<%= render variable_fields, :f => builder %>
<% end %>
</div>
created report/_variable_fields.html.erb
<p>
<%= f.label :name %>
<%= f.text_field :name, :class => 'text_field' %>
<p>
model for report_variable
class ReportVariable < ActiveRecord::Base
attr_accessible :report_id, :value, :variable_id
has_and_belongs to many :reports
has_and_belongs to many :variables
end
model for report
class Report < ActiveRecord::Base
attr_accessible :employeeName
has_many :report_variable
has_many :variable
accepts_nested_attributes_for :report_variable
accepts_nested_attributes_for :variable
end
Sorry if it's a simple question, im pretty new to rails.
Welcome to Rails!
I think the simple answer is the fields aren't showing up because there aren't any nested records. You can probably get around that by uncommenting the variable line as you have it:
def new
#report = Report.new
#report.variables.build #this line creates 1 new empty variable, unsaved.
respond_to do |format|
format.html # new.html.erb
format.json { render json: #report }
end
end
If you want more than one variable, call something like:
3.times { #report.variables.build }
That way the #report object you're placing in the form helper will have three variables on it. This should get you moving again, the harder thing is going to be adding ajax addition / removal of variables, but if you know how many there are in advance you don't have to deal with that.
Good luck!

Rails 3.0 Skip validations for nested attributes

I need to be able to save a record without running validations on itself or its nested attributes. I'm stuck in Rails 3.0, and I cannot update to a newer version.
I have a report, each report has many responses (answers to questions). The responses are nested in the report form.
There are two ways the user should be able to save the report: Submit for review, where all validations are run, and Save And Finish Later, where no validations are run for the report or the nested responses. This needs to work for both create and update actions.
I am currently trying to use conditional validations. This works for update but not create. The problem is this line:
validate :has_answer_if_required, :if => Proc.new { |response| !response.report.finish_later? }
The report doesn't exist yet, so active record can't find this responses's report. That's where it crashes.
There are a lot some suggested solutions for this problem, but I couldn't get them working in Rails 3.0. update_attributes(attributes, :validate => false), for instance, is not available in Rails 3.0.
So, how do I skip the validations in the nested attributes?
class Report < ActiveRecord::Base
has_many :responses, :order => "created_at asc", :autosave => true
accepts_nested_attributes_for :responses
...
end
class Response < ActiveRecord::Base
belongs_to :report
validates_associated :report
validate :has_answer_if_required, :if => Proc.new { |response| !response.report.finish_later? }
validate :correct_answer_or_comment, :if => Proc.new { |response| !response.report.finish_later? }
end
class ReportsController < BaseController
def update
#report = Report.find(params[:id])
#report.attributes = params[:report]
if params[:finish_later].nil?
#report.update_attribute(:finish_later, false)
if #report.save!
redirect_to :action => :index
else
render :template => "reports/edit"
end
else
#report.finish_later = true
#report.save(:validate => false)
redirect_to :action => :index
end
end
def create
#report = Report.new(params[:report])
if params[:finish_later].nil?
#report.finish_later = false
if #report.save!
redirect_to :action => :index
else
render :template => "reports/edit"
end
else
#report.finish_later = true
#report.save!(:validate => false)
redirect_to :action => :index
end
end
end
Not sure if it will work with nested attributes, though I think it should... but give ValidationSkipper a try:
https://github.com/npearson72/validation_skipper
Just make sure you call skip_validation_for on the object you want to skip. Since nested attributes pass behavior to their children, you might be able to call this method directly on the parent object. Give it a try.

mongoid save embedded documents

I'm trying to build up on the following tutorial from railscast:
http://railscasts.com/episodes/196-nested-model-form-part-1
I'm trying to make everything work with mongodb and mongoid.
the scenario is:
I want to creates events linked to a location. Each events (dance class) contains many lessons.
So I thought that an embedded relationship would be perfect.
Here are my models
model Lesson
class Lesson
include Mongoid::Document
include Mongoid::Slug
field :name, :type => String
embedded_in :event
slug :name
end
model Event
class Event
include Mongoid::Document
include Mongoid::Slug
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
field :name, :type => String
field :description, :type => String
field :date, :type => DateTime
validates_presence_of :name
has_one :venue
referenced_in :venue
embeds_many :lessons
slug :name
end
model Venue
class Venue
include Mongoid::Document
include Mongoid::Slug
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
field :name, :type => String
field :location, :type => String
validates_presence_of :name, :location
belongs_to :event
slug :name
end
event controller
def create
#event = Event.new(params[:event])
if #event.save
flash[:notice] = 'Event was successfully created.'
end
respond_with(#Event, :location => events_url)
end
def update
# #event = Event.find(params[:id])
#event = Event.find_by_slug(params[:id])
if #event.update_attributes(params[:event])
flash[:notice] = "Event was succesfully updated"
end
respond_with(#event)
end
Then I have my Event view where I can create events and link it to a Venue. But I'd like to be abe to create the lessons from the Event view/model.
so I used the fields_for to generate a field linked to the Lessons model.
= form_for #event do |f|
.field
= f.label :name
%br/
= f.text_field :name
.field
= f.label :description
%br/
= f.text_area :description
.field
= f.label :venue_id
%br/
= f.collection_select :venue_id, Venue.all, :id, :name
.field
= f.label :date
%br/
= f.datetime_select :date
%h3 Add a Class
= f.fields_for :lessons do |builder|
= render "lesson_fields", :f => builder
.actions
= f.submit 'Save'
When I create or edit a new event I get an error message:
undefined method `extract_id' for "test":String
But the request parameter message on the error page shows my lessons value in the Event document.
"lessons"=>{"name"=>"test name lesson"}
When I remove the fields_for line, everything works fine. But then i don't know how to save the value for the nested documents.
I have same problem with embeds_many, but when i try change to has_many. It works!. Maybe you can try too.
can you post the exact code you use to create the Event, including parameters?
which version of Mongoid and Rails are you using?
First thing I noticed is that the following parameter hash does not match your Lessons model:
"lessons"=>{"content"=>"test name lesson"} # this looks wrong
this should be:
"lessons"=>{"name" => "test name lesson"}
Looks like your lessons form has the wrong label for the text input field .. it should be :name , not :content
To dry things up, you might want to try if the 'nested_form' gem works for you:
after installing the gem, use the nested_form_for instead of form_for in your view.
Check here for a more detailed description:
How can I handle this type of multi level forms in rails
See:
https://github.com/ryanb/nested_form (it's also referenced in the RailsCast you mentioned)
You also might want to check this:
field_for and nested form with mongoid
The conclusion of this story is...
I removed everything related to mongoid_slug and it started to work.
I then put everything back as it was to try to find out how to make it work with mongoid_slug and it just worked, like out of the box.
:(
Please include the following code in model event.rb
**accepts_nested_attributes_for :lessons**
This will fix your problem

Adding ajax to Rails 3 form_for

I'm learning to program and got a form running in my Rails 3 app. Now I'm attempting to add ajax to the form so the page doesn't reload after submitting.
I've followed the numerous tutorials but can't quite seem to figure out how to bring it together. The form adds new Objects to the Profile through the following model:
class Profile < ActiveRecord::Base
has_many :objects
end
class Object < ActiveRecord::Base
belongs_to :profile
end
My form in views/profiles/_object_form.html.erb:
<%= form_for(#object, :remote => true) do |f| %>
<% end %>
Where the form and its created objects are rendered in my views/profiles/_about.html.erb:
<div id="newObjects">
<%= render :partial => 'object_form' %>
</div>
<div id="objectList">
<%= render :partial => 'object', :collection => #profile.objects, :locals => {:object_count => #profile.objects.length) %>
</div>
In my objects_controller.rb I have the following create action:
def create
#object = Object.new(params[:object].merge(:author_id => current_user.id))
respond_to do |format|
if #object.save!
format.html {redirect_to profile_path(#object.profile) }
format.js { render }
else
format.html { redirect_to #profile, :alert => 'Unable to add object' }
end
end
end
In views/objects/create.js.erb:
$('#objectList').append("<%= escape_javascript(render #profile.object)) %>");
So I have a form calling an action in another controller to which I want to add ajax. What happens at the moment is that I need to reload the profile to show the newly created object. What am I doing wrong?
CLARIFICATION: Other than the create action in the ObjectsController, I only reference #object once elsewhere. That's in the ProfilesController's show action:
def show
#profile = Profile.find(params[:id])
#superlative = #profile.superlatives.new`
end
Not sure if this is a full code snippet for your create action, but looks like you are trying to call render on an instance variable that doesn't exist... #profile is never set in the create method in the ObjectController...
Perhaps you meant to type $('#objectList').append("<%= escape_javascript(render #object)) %>");
Also noticed that in your existing code you're making a call to render #profile.object, but the Profile class has a has_many relationship with your Object class, so if that was the right code, then you should type render #profile.objects (plural, not singular).
But I would think you would likely want the code I mentioned above, since you are appending onto the list of objects, not rendering the list again?

Rails 3 model with accepts nested attributes not updating children

I've got a model setup where a user can create a quiz with many questions and many answers on each question
The models look like this:
model Page < AR::Base
end
model Quiz < Page
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true
end
model Question < AR::Base
belongs_to :quiz
has_many :answers
accepts_nested_attributes_for :answers, :allow_destroy => true
end
model Answer < AR::Base
belongs_to :question
end
And my form looks like this:
= form_for #quiz do |f|
f.fields_for :questions do |qf|
# fields omitted, have fields for id, content, etc
qf.fields_for :answers do |af|
# fields omitted, have fields for id, answer, etc
f.submit 'save'
Everything works wonderfully when I edit just the quiz or when I add new questions and answers, but when I edit existing questions and answers, the changes aren't persisted in the DB. I can see the correct nested parameters being sent into the controller and when inspected the #quiz after calling update_attributes it shows the updated questions and answers but they aren't being persisted after the page is updating.
I've never had this sort of issue before and am having trouble spotting the cause, can anyone share some insight?
Thanks!
As requested, controller code: (Quiz is an STI subclass of Page)
PagesController < ApplicationController
def update
#page = #section.pages.find(params[:id])
if #page.update_attributes(params[#page.type.downcase.underscore])
redirect_to online_course_section_pages_path(#online_course, #section), :notice => "Your page has been updated"
else
render :edit
end
end
end
EDIT:
Found the problem was because of using #page.type.downcase.underscore instead of #page.type.underscore.downcase so update attributes was being passed nil instead of the actual data
Found the problem was because of using #page.type.downcase.underscore instead of #page.type.underscore.downcase so update attributes was being passed nil instead of the actual data