ruby-on-railsrubyform-fornested-form-for

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:

  1. 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...

  2. 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.


Solution

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