Why Aren't These Errors Rendering For Nested Associations? - ruby-on-rails

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)

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.

Uploading 2 images in rails using two field

I have a form for a blog, and I would like to have two field for images. One image being the cover (in Show) and another image will serve as a preview (in index).
My form looks as follow:
<%= semantic_form_for #blog, :html => { :multipart => true } do |f| %>
<%= t :Choose_File_for_cover %> <%= f.file_field :image_path, id: "avatar-upload2", required: true %>
<img id="img_prev3" width="100%" height=200 src="#" alt="your image" class="img-thumbnail hidden"/>
<%= t :Choose_File_for_homepage %> <%= f.file_field :homepagepic, id: "avatar-upload3", required: true %>
<%= f.hidden_field :image_path_cache %>
<%= f.hidden_field :homepagepic_cache %>
<%= f.actions do %>
<%= f.action :submit, :as => :input %>
<% end %>
<% end %>
My model looks like:
class Blog < ApplicationRecord
belongs_to :user
acts_as_taggable
mount_uploader :image_path, BlogUploader
mount_uploader :homepagepic, BlogcoverUploader
end
It works well when I only have the image_path (the cover), but when I add a new field for homepagepic, i get a ROLLBACK at validation.
Can someone help me on how to select files through two separate fields on the same form please.
Thank you
The code you've provided is very sparse and it would be helpful to see a little bit more (e.g. the controller and the uploader).
I can, however, hazard a guess: image_path is an existing helper method provided by Rails (see https://api.rubyonrails.org/classes/ActionView/Helpers/AssetUrlHelper.html#method-i-image_path). I have absolutely no idea what happens when you use this as a name for a form field. It could also be because you declare your submit button to be an input (I've only ever seen and used as: :button for f.action :submit).
So overall, I would pick the following approach:
rename your upload fields to cover_image and the other one to preview_image (that's what you've described in your posts as their respective purpose, so you should name them accordingly)
change the submit to a button and remove all the noise from your template and start with the bare minimum: the two upload fields and nothing else (see sample code below – note that I haven't tested it but it should work or be very close to working)
after that works, start adding back the noise (i.e. the translations, the cache fields etc.)
Test that it still works after every step. If you can write a Capybara test, do that – otherwise test it manually.
If you have questions, feel free to ask.
<%= semantic_form_for #blog, html: { multipart: true } do |f| %>
<%= f.file_field :cover_image %>
<%= f.file_field :preview_image %>
<%= f.actions do %>
<%= f.action :submit, as: :button %>
<% end %>
<% end %>
class Blog < ApplicationRecord
belongs_to :user
acts_as_taggable
mount_uploader :preview_image, BlogUploader
mount_uploader :cover_image, BlogcoverUploader
end
As the previous poster said it's hard to debug your code without all the pieces to the puzzle. A ROLLBACK is happening because one or more validations failed.
Any time you have a ROLLBACK you can add a ! to the create or update method being called on the object being rolled back and ActiveRecord will throw an error telling you why the ROLLBACK happened instead of failing gracefully.
Once you know why your object isn't persisting you can check the params of the controller action that form is submitting to. Perhaps you forgot to whitelist a param via strong params?

Rails form with nested attributes. text_field not appearing

Issue: I have a nested fields_for text_field not appearing, I am not sure what I have been done wrong.
Goal: While creating a record, iterate through a model with preset variables, and save a file (testing with text_field) to a join table which saves both the preset variables and the forms record ID
Models:
class PrintLocation < ApplicationRecord
has_many :shop_products, through: :shop_product_print_files
has_many :shop_product_print_files
accepts_nested_attributes_for :shop_product_print_files
end
class ShopProductPrintFile < ApplicationRecord
belongs_to :shop_products
belongs_to :print_locations
end
class ShopProduct < ApplicationRecord
...
has_many :shop_product_print_files
has_many :print_locations, through: :shop_product_print_files
accepts_nested_attributes_for :print_locations
accepts_nested_attributes_for :shop_product_print_files
...
end
Form:
<%= form_for #shop_product do |f| %>
<%= f.collection_select :product_id, #products, :id, :sku %>
<% PrintLocation.all.each do |print_location| %>
<%= print_location.title %>
<%= f.fields_for :shop_product_print_files do |a| %>
<%= a.text_field :print_file %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
With this, the text_field doesn't appear but the print_location.title's do appear. There are no errors with this.
While saving the #shop_product, I want to be able to iterate through the possible print_location variables, which are defined, and then for each possible print_location, to then be able to upload a file (text_field for testing), and then save that to the ShopProductPrintFile model which has shop_product_id and print_location_id and print_file attributes.
Is there something I am misunderstanding for how to use fields_for?
Shop Product Controller:
Create:
#shop_product = ShopProduct.new(shop_product_params)
shop = Shop.find(params["shop_product"]["shop_id"])
product = Product.find(params["shop_product"]["product_id"]) #shop_product.product_id = product.id
#shop_product.shop_id = shop.id
respond_to do |format|
if #shop_product.save!
...
Update:
#shop_product = ShopProduct.find_by(store_variant_id: params["shop_product"]["store_variant_id"])
#product = Product.find(params["shop_product"]["product_id"])
Strong Params:
def shop_product_params
params.require(:shop_product).permit(:product_id, :store_product_id, :shop_id, :store_variant_id, :sync, :shop_product_print_file_attributes[:id, :print_files, :print_location_ids => [], :shop_product_ids => []], {print_location_ids: []})
end
UPDATE 2:
Update and Create Method:
#shop_product.shop_product_print_files.build
form:
<% PrintLocation.all.each do |print_location| %>
<%= print_location.title %>
<%= f.fields_for :shop_product_print_files_attributes do |a| %>
<%= a.text_field :print_file %>
<%= a.hidden_field :print_location_id, value: print_location.id %>
<%= a.hidden_field :shop_product_id, value: shop_product.id %>
<% end %>
<% end %>
params:
def shop_product_params
params.require(:shop_product).permit(:shop_product_print_files_attributes => [:ids => [], :print_files => [], :print_location_ids => [], :shop_product_ids => []])
end
error:
Shop product print files shop products must exist
Shop product print files print locations must exist
params that pass:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"u/c103465uNCjF/trYrMleqxJ8b9wyLbU/vjPK4llYtCg/ODj92q5MN24==", "shop_product"=>{"sync"=>"1", "product_id"=>"3", "shop_product_print_files_attributes"=>{"print_file"=>"", "print_location_id"=>"6", "shop_product_id"=>"42"}, "store_product_id"=>"191234345", "store_variant_id"=>"15341234273", "id"=>"42"}, "commit"=>"Sync", "id"=>"42"}
The models haven't changed.
Print file in params still blank?
UPDATE 3:
**using this form: thanks to #arieljuod **
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title # get the print location from the association %>
<%= ff.hidden_field :print_location_id # save the print_location_id as a hidden field %>
<%= ff.file_field :print_file # file input %>
<% end %>
with this in the new and method housing the view:
#shop_product = ShopProduct.new
PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)}
works on create.
Issue still arises due to not knowing the ID of the ShopProduct until the page loads due to API and there is a possibility of being multiple IDs on one page.
I use:
<% if #shop_products.find_by(store_variant_id: variant.id) %>
<% shop_product = #shop_products.find_by(store_variant_id: variant.id) %>
<%= form_for shop_product do |f| %>
...
Which, variant comes from a loop defined by an API:
<% #in_store_variants.each do |variant| %>
Now when using shop_products (from when shop_product already exists from finding by the variant.id), the fields_for won't appear. Assuming this is because no records exist in relation. Only if a shop_product.shop_product_print_files exist, will they appear.
The only work around, at this time to my knowledge, is to save all print_locations but use a boolean for which are actually active, or search for which print_locations have an ID attached. But i would rather not do it that way and just save which print_locations are chosen on create (chosen by uploading a print_file).
To "fix" this issue, I:
added accepts_nested_attributes_for reject_if: proc { |attributes| attributes['print_file'].blank? } which doesn't save ShopProductPrintFile's unless the print_file field is submitted with something...
use this form (2 forms depending on if exists or not)
<% if #shop_products.find_by(store_variant_id: variant.id) %>
<%= form_for shop_product do |f| %>
<% PrintLocation.all.each{|p| shop_product.shop_product_print_files.build(print_location: p)} %>
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title %>
<%= ff.hidden_field :print_location_id %>
<%= ff.text_field :print_file %>
<% end %>
<%= f.submit "Sync" %>
<% end %>
<% else %>
<%= form_for #shop_product do |f| %>
<% PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)} %>
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title %>
<%= ff.hidden_field :print_location_id %>
<%= ff.text_field :print_file %>
<% end %>
...
The issue with 2 is i have have PrintLocation 1,2,3 associated, it will show 9 fields, the 1,2,3 ready for update, and the 6 ready for create.
is it possible to call the PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)} on already created ShopProducts's for where a shop_product_print_file doesn't exist in relation to the possible print location.
So for example...
Created ShopProduct with print location, 1,2,3 (out of 6 possible)
Now, shop_product_print_location where print_location exists will show for updating in the form, so thats 1,2, and 3. How can I have it so the other 3 that weren't created now show to update the ShopProduct and create new ShopProductPrintFile's? so it is possible to update the ShopProduct to have more print_locations to the shop_product_print_file model.
I have a nested fields_for text_field not appearing, I am not sure
what I have been done wrong.
You should add this line in your create action
#shop_product = ShopProduct.new(shop_product_params)
#shop_product.shop_product_print_files.build #this one
Also change shop_product_print_file_attributes to shop_product_print_files_attributes to avoid any further errors.
You have to tell rails which PrintLocation to use on each iteration since your object does not have any
<%= f.fields_for :shop_product_print_files, print_location do |a| %>
I'm not really sure if that's what you want, but the field will appear.
EDIT: so, I think you need something like this:
On the controller
#shop_product = something_to_get_the_product
PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)}
I prefer to do this here, I don't like that logic on the view
Now you have all the possible print location prebuilt on the shop product object
On the form
# note here the multipart option to allow files
<%= form_for #shop_product, multipart: true do |f| %>
<%= f.collection_select :product_id, #products, :id, :sku %>
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title # get the print location from the association %>
<%= ff.hidden_field :print_location_id # save the print_location_id as a hidden field %>
<%= ff.file_field :print_file # file input %>
<% end %>
<%= f.submit %>
<% end %>

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 %>

Display Errors For Nested Form

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?

Resources