Display Errors For Nested Form - ruby-on-rails

I have a nested form in my view;
<%= simple_form_for #form, url: url do |f| %>
<%= f.input :name %>
<%= f.input :description, as: :text %>
<%= f.fields_for :account, { :class => 'field' } do |ff| %>
<%= ff.collection_radio_buttons(:account_type_id, AccountType.all, :id, :name, selected: ff.object.account_type_id) { |b| radio_button_builder(b) } %>
<% end %>
Here is my Form object (extending Reform);
class ProgrammeForm < ApplicationForm
properties :name, :description, validates: {presence: true}
property :account do
property :account_type_id
validates :account_type_id, presence: true
end
end
If I submit the form without filling in any fields, then my <% if #form.errors.any? %> partial will print out three errors;
3 Errors prohibited this from being saved:
Name can't be blank
Description can't be blank
Account account type can't be blank
But there is no "error-looking" HTML on my nested form (the account_type stuff). Name and description are fine (as those are fairly straight forward). But;
fields_for doesn't put a div wrapper around the radio buttons collection to begin with.
There is no other indication that there was an error on this field (like with name and description).
How can I address these two points?

Related

Simple Form non associated model checkbox

I'm running into a couple of problems, some of which, I think I have found a resolution. I'm trying to create a checkbox which will copy already filled in information to another form.
Let me try and paint the picture. I have a HomeAddress & OfficeAddress that belong_to a User. While many people are working from home these days, these addresses can both be the same, so when creating a User, I want them to be able to check a box which will copy the address information from HomeAddress to the OfficeAddress input fields.
<%= f.simple_fields_for :home_address do |f| %>
<%= f.input :address %>
<%= f.input :city %>
<%= f.input :state %>
<%= f.input :zipcode %>
<% end %>
<%= f.simple_fields_for :office_address do |f| %>
<%= f.input :copy_home_address, as: :boolean, checked_value: true, unchecked_value: false, input_html: { checked: false id: 'copyPrimaryLocationAddress' } %>
<%= f.input :address %>
<%= f.input :city %>
<%= f.input :state %>
<%= f.input :zipcode %>
<% end %>
Couple of questions and problems I'm running into to start. Obviously adding the default value checked: false allows me to get around having to have this attribute exist on my OfficeAddress model. I am aware that I can create my own custom one which I'm fine with as well: app/inputs/fake_checkbox_input.rb(unsure how to get it to work):
class FakeCheckboxInput < SimpleForm::Inputs::StringInput
# This method only create a basic input without reading any value from object
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
tag_name = "#{#builder.object_name}[#{attribute_name}]"
template.check_box_tag(tag_name, options['value'] || 1, options['checked'], options)
end
end
This seems to get by the errors and association to a model that a form field has to have. However, the value ALWAYS stays the same. I'm trying to get it so that the value will change depending on when/if the checkbox is checked. Not to mention how to copy the information from the HomeAddress to the OfficeAddress which is a separate issue I dont know how to resolve.
Any tips on how to resolve or where to start would be helpful. I'm reading through the SimpleForm documentation but it just isn't making sense for my given circumstance.

Model with nested attributes doesn't show fields_for in field that is not an attribute in the model

I have an Exam and an ExamBattery that is just a collection of Exams. They have a has_and_belong_to_many declaration for each other, and ExamBattery accepts nested attributes for Exam, like so:
class Exam < ApplicationRecord
has_and_belongs_to_many :exam_batteries
validates_presence_of :name
end
class ExamBattery < ApplicationRecord
has_and_belongs_to_many :exams
accepts_nested_attributes_for :exams, reject_if: lambda { |attrs| attrs['name'].blank? }
validates_presence_of :name
end
When I create a new Exam, I want to be able to assign it to one or many ExamBatteries, so in ExamsController I whitelisted the array exam_battery_ids to accept multiple ExamBatteries to assign them to the current Exam (no other change was made, the controller is just from the scaffold):
def exam_params
params.require(:exam).permit(:name, :description, :order, :price, exam_battery_ids: [])
end
Also, in the view exams/new I added a multiple select to send the desired exam_battery_ids as params:
<%= form_with(model: exam, local: true) do |form| %>
# ... typical scaffold code
<div class="field">
<% selected = exam.exam_batteries.collect { |eb| eb.id } %>
<%= form.label :exam_battery_ids, 'Add batteries:' %>
<%= form.select :exam_battery_ids,
options_from_collection_for_select(ExamBattery.all, :id, :name, selected),
{ prompt: 'None' },
multiple: true %>
</div>
<% end %>
The idea is to be able to create a new ExamBattery with new Exams in it, in the same form (I haven't wrote that part yet, I can only edit for now). Also, when I edit an ExamBattery I want to be able to edit its Exams and even assign them to other ExamBatteries (if I select 'None', or JUST another exam battery, it would stop being assigned to the current ExamBattery), so in exam_batteries/edit (actually, the form partial in it) I have this code:
<%= form_with(model: exam_battery, local: true) do |form| %>
# ... normal scaffold code
<div class="field">
<!-- it should be exam_battery[exams_attributes][#_of_field][order] -->
<!-- it is exam_battery[exam_battery_ids][] -->
<% selected = exam_battery.exams.map { |exam| exam.id } %>
<%= form.label :exam_battery_ids, 'Edit batteries:' %>
<%= form.select :exam_battery_ids,
options_from_collection_for_select(ExamBattery.all, :id, :name, selected),
{ prompt: 'None' },
multiple: true %>
</div>
<% end %>
And in ExamBatteriesController I whitelisted the exam_batteries_attributes, with exam_battery_ids: [] as a param:
params.require(:exam_battery).permit(:name, :certification, exams_attributes: [:name, :description, :order, :price, exam_battery_ids: []])
But when in the ExamBattery form I try to edit the Exam's exam_batteries, the info doesn't update, because the params are like this:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"blah", "exam_battery"=>{"name"=>"Battery1", "certification"=>"test1", "exams_attributes"=>{"0"=>{"name"=>"Exam1", "description"=>"", "order"=>"", "id"=>"3"}, "1"=>{"name"=>"Exam2", "description"=>"", "order"=>"", "id"=>"4"}, "2"=>{"name"=>"Exam3", "description"=>"", "order"=>"", "id"=>"5"}}, "exam_battery_ids"=>["", "", "", "", "", "3"]}, "commit"=>"Update Exam battery", "id"=>"3"}
The exam_battery_ids are sent as a different param because the select name is exam_battery[exam_battery_ids][] instead of something like exam_battery[exams_attributes][0][name], as it happens with the other fields. How can I fix that?
Thanks.
I had an error in the form. In exam_batteries/edit I didn't notice I was using the form_with variable (form) and not the fields_for variable (builder), so it should be like this:
<div class="field">
<!-- it should be exam_battery[exams_attributes][0][order] -->
<!-- it is exam_battery[exam_battery_ids][] -->
<% selected = exam_battery.exams.map { |exam| exam.id } %>
<%= builder.label :exam_battery_ids, 'Escoge una batería' %>
<%= builder.select :exam_battery_ids,
options_from_collection_for_select(ExamBattery.all, :id, :name, selected),
{
include_hidden: false,
prompt: 'Ninguna'
},
multiple: true %>
</div>
With that it should work.
The only issue now is that I can't get the selected batteries when I show them in the fields_for, but I'm working on it.
UPDATE: I can show the current exam_batteries of the exam in the nested form by replacing the selected variable in the view with this:
<% selected = exam_battery.exams[builder.options[:child_index]].exam_batteries.map { |eb| eb.id } %>
If you know about a cleaner method, please let me know.

Can one simple_form create instances of multiple models?

I got simple_form for testrun model with multiple checkboxes, that save an array of testcases in a model field
app/views/testruns/_form.html.erb
<%= simple_form_for #testrun do |f| %>
<%= f.input :testcase, as: :check_boxes,
collection: [["testcase1", :testcase1], ["testcase2", :testcase2], ... ]%>
<%= f.submit %>
<% end %>
It works fine, but from now I need to create another model called testcase. After submitting form, besides creating a new testrun instance, I need to create testcase instances which depends on every flag checked.
Any idea how can I do it?
You need to use accepts_nested_attributes_for and simple_fields_for. Assuming you have has_many :testcases in Testrun and the field name of Testcase is name, the below steps should put you in the right direction.
#app/models/testrun.rb
accepts_nested_attributes_for :testcases
#app/controllers/testrun_controller.rb
def new
#testrun = Testrun.new
#testrun.testcases.build
end
private
def testrun_params
params.require(:testrun).permit(:field1, :field2.., testcases_attrubtes: [name: []])
end
#app/views/testruns/_form.html.erb
<%= simple_form_for #testrun do |f| %>
<%= f.simple_fields_for :testcases do |testcase| %>
<%= testcase.input :name, as: :check_boxes,
collection: [["testcase1", :testcase1], ["testcase2", :testcase2], ... ]%>
<% end %>
<%= f.submit %>
<% end %>

Why Aren't These Errors Rendering For Nested Associations?

I have a MediaItem model that has two has_one associations. I validate that one or the other are present using:
validates_presence_of :photo, allow_nil: true
validates_presence_of :video, allow_nil: true
validate :photo_or_video_present
def photo_or_video_present
if !(photo.blank? ^ video.blank?)
errors['photo_attributes.image'] << 'Select a photo or a video'
errors['video_attributes.uid'] << 'Select a photo or a video'
end
end
Which gives me the following error object when the form is rendered:
<ActiveModel::Errors:0x007f99c8a71fc0
#base=#<MediaItem id: nil,
title: "asdadasdasd",
slug: "asdadasdasd",
gallery_id: 1,
created_at: nil,
updated_at: nil>,
#messages={:"photo_attributes.image"=>["Select a photo or a video"],
:"video_attributes.uid"=>["Select a photo or a video"],
:title=>[] }>
However these errors don't show up in the nested models. The form looks like this:
<div class="FormWrapper">
<%= simple_form_for [:admin, #gallery, #media_item], html: { class: "#{action_name.titleize}PhotoForm" } do |f| %>
<%= f.input :title %>
<div class="PhotoInputs">
<h3>Photo</h3>
<%= f.simple_fields_for :photo do |ff| %>
<%= ff.input :image, as: :file_upload, input_html: {preview: #media_item.photo.image} %>
<% end %>
</div>
<div class="VideoInputs">
<h3>Video</h3>
<%= f.simple_fields_for :video do |ff| %>
<%= ff.input :provider, as: :radio_buttons, collection: Video::PROVIDERS %>
<%= ff.input :uid %>
<% end %>
</div>
<hr>
<%= render partial: 'shared/form_submit', locals: {resource: #media_item} %>
<% end %>
</div>
Why are these inline errors failing to show up?
Note: There is no absolutely no problem with the functioning of the form. I can create and edit both model and nested model without issue.
These 2 validations do nothing validates_presence_of :photo, allow_nil: true validates_presence_of :video, allow_nil: true because you are validating the presence but allowing it to not be present. I would just add the error to :base and render that in the view. e.g.
validates :has_photo_or_video
def has_photo_or_video
if (photo.blank? ^ video.blank?)
errors.add(:base,"Please select a photo or video")
end
end
Then in the view
<% f.errors[:base].each do |message| %>
<span>message</span>
<% end %>
I have not seen an implementation of inline errors like you describe although you could try something like this as well:
def has_photo_or_video
if !(photo.blank? ^ video.blank?)
photo.errors.add(:image,"Please select a photo or video")
video.errors.add(:uid,"Please select a photo or video")
end
end
Although I make no representation as to whether or not this will work.
Your issue is that it is looking for your errors to be contained in a nested hash like
{photo_attributes: {image: ["Please select a photo or video"]},
video_attributes: {uid: ["Please select a photo or video"]} }
The only way I have seen this done is by validating on the associated model which is not possible in your case since it is a one or both type procedure. At least as I understand it.
If this is an either or type procedure meaning a MediaItem is either a Photo or a Video but not both then I would suggest looking into polymorphism as it would make this procedure far easier.
Or even better still scrap MediaItem all together and add the title attribute to Photo and Video and then create each one separately as this seems like the easiest route. (You could still define a method for media_items that would grab all of these)

Rails: form_for with field for Hash

i've got model with:
class Product < ActiveRecord::Base
attr_accessible :category, :description, :img_url, :name, :price, :quantity, :tags
serialize :tags, Hash
end
and try to make form for it
<%= form_for #product do |f| %>
<%= f.label :"tags[:condition]_new", "new" %>
<%= f.radio_button :"tags[:condition]", "New", checked: true %>
<%= f.radio_button :"tags[:condition]", "Used" %>
<% end %>
unfortunately it rails raise
undefined method `tags[:condition]' for #Product:0x007fd26d965810>
<%= f.radio_button :"tags[:condition]", "Used" %> <-- ONLY FOR 2ND LINE. first is okey. WHY?!
and I can't figure out why its trying to put method on it. Has anyone idea how to make proper field for hash value?
+ Why it fails only on 2nd f.radio_button and i passes first one?
This is because you are not setting any value for 2nd radio button, try this and it will work fine.
<%= f.radio_button :"tags[:condition]", "Used", checked: false %>
As if you will not pass any value, then FormHelper class will call 'name[:condition]' method on #product to get its corresponding value, though there is no method defined in the model it raises exception.

Resources