1

I have a simple_fields_for form that is rendered within an iterator, like so:

<%= simple_form_for @port_stock, url: port_stocks_sell_order_path, method: :post, html: { class: "form-inline" } do |f| %>

  <% @buy_port_stocks.each do |port_stock| %>
    <%= f.simple_fields_for :closed_positions, html: { class: "form-inline" } do |c| %>
      <div class="form-group">
        <%= c.input_field :port_stock_id, as: :hidden, value: port_stock.id %>
        <%= c.input_field :num_units, id: "sell-ps-#{port_stock.id}", placeholder: "Number of Units", class: "form-control mx-sm-3" %>
         <%= c.input_field :closed_price, id: "sale-price-for-ps-#{port_stock.id}", placeholder: "Sale Price", class: "form-control mx-sm-3" %>
       </div>
   <% end %>
  <% end %>
<% end %>

In my controller I have this:

@port_stock = current_user.port_stocks.friendly.find(params[:id])
@buy_port_stocks = current_user.port_stocks.buy.joins(:stock).where(stocks: { ticker: @stock.ticker})
@cp = @port_stock.closed_positions.build

My PortStock.rb model:

  has_many :closed_positions, dependent: :destroy
  accepts_nested_attributes_for :closed_positions, allow_destroy: true

My ClosedPosition.rb model:

class ClosedPosition < ApplicationRecord
  belongs_to :closer, class_name: "PortStock", foreign_key: "closer_id"
  belongs_to :closed, class_name: "PortStock", foreign_key: "port_stock_id" 
end

The above works perfectly for the @port_stock records that have no closed_positions.

For example, that form is rendered like this:

good-rendering-of-simple_form_for

Note that the Number of Units and Sale Price field only show up once in each row (which is what I expect).

However, once I create a closed_position on any PortStock it creates two issues:

First Issue

It pre-fills the existing closed position as a field, and then renders another blank field for closed_positions, i.e. like this:

first-issue

What I want to happen is for it to just render the new form and not re-render the existing closed_position values on each row. The user should not be able to edit existing closed positions in this form.

Second Issue

Whenever there are multiple closed positions, it renders the wrong ones in every row.

wrong-values-rendered-for-each-closed-position

Note how each value that is rendered says num_units: 100 && price: 8.0, look at the console output of those same closed_positions:

=> [#<ClosedPosition:0x00007ff13e77c6d0
  id: 9,
  closer_id: 2,
  port_stock_id: 17,
  num_units: 100,
  closed_price: 8.0,
  ticker: "CAC",
 #<ClosedPosition:0x00007ff13e77c2e8
  id: 10,
  closer_id: 3,
  port_stock_id: 18,
  num_units: 10,
  closed_price: 7.95,
  ticker: "CAC",
 #<ClosedPosition:0x00007ff13e77c018
  id: 11,
  closer_id: 10,
  port_stock_id: 19,
  num_units: 50,
  closed_price: 7.9,
  ticker: "CAC",

The correct values are actually:

  1. Num Units: 100 && Price: 8.0
  2. Num Units: 10 && Price: 7.95
  3. Num Units: 50 && Price: 7.9

I don't understand why it outputs the same value for all the port_stock objects.

How do I fix these two issues in my simple_fields_for form?

marcamillion
  • 32,933
  • 55
  • 189
  • 380
  • Yeah, I always have this issue when nesting associated models. Can you do `<%= f.simple_fields_for @cp, html: { class: "form-inline" } do |c| %>`? – Chiperific Jun 27 '18 at 04:21
  • @Chiperific Great thinking and suggestion. So at first glance that seems to fix it, i.e. it gets rid of the duplicate fields. The issue though is that `@cp` is always declared in the controller and it's related to `@port_stock` as opposed to the local `port_stock` variable within the iterator. – marcamillion Jun 27 '18 at 05:18
  • @Chiperific Also weirdly, I am now getting this error: `Unpermitted parameter: :closed_position`, even though I have declared it in my strong params like so: `params.require(:port_stock).permit(:portfolio_id, :closed_position, closed_positions_attributes: [:num_units, :closer_id, :port_stock_id, :closed_price, :_destroy])`. Thoughts? – marcamillion Jun 27 '18 at 05:37
  • @Chiperific Your suggestion worked, but rather than removing `:closed_positions` from the `simple_fields_for` form handler, I simply added `@cp` to the form handler as well -- i.e. `<%= f.simple_fields_for :closed_positions, @cp, html: { class: "form-inline" } do |c| %>` and that worked. I got that suggestion from another SO question. So if you want to add that as the answer, I will accept it. Thanks for the suggestion! – marcamillion Jun 27 '18 at 07:31

1 Answers1

1

OK, let me coach this with my logic isn't solid, but I'm basing it on ActionView::Helpers::FormHelper

Regardless of simple_form or standard Rails helpers, there's a difference between passing an associated record and the associated model.

<%= form_for @record %>
  <%= fields_for @associated_record %>

vs.

<%= form_for @record %>
  <%= fields_for :associated_model %>

And I believe it has to do with instantiating a new record when one isn't present.

So, my first swing in the comments was to suggest using the more specific record variable:

:closed_positions @cp

Like thus:

<%= simple_form_for @port_stock, url: port_stocks_sell_order_path, method: :post, html: { class: "form-inline" } do |f| %>
  <% @buy_port_stocks.each do |port_stock| %>
    <%= f.simple_fields_for @cp, html: { class: "form-inline" } do |c| %>
      ...

As you commented, this isn't a perfect solution, but does solve the duplicate fields.

But, as your controller indicates, @cp isn't a direct association to @buy_port_stocks, which is what the form is looping through.

So, the solution you really discovered is to declare both, which is allowed:

Fields may reflect a model object in two ways - how they are named (hence how submitted values appear within the params hash in the controller) and what default values are shown when the form the fields appear in is first displayed. In order for both of these features to be specified independently, both an object name (represented by either a symbol or string) and the object itself can be passed to the method separately

And the docs suggest declaring both, which in your case would look like this:

<%= simple_form_for @port_stock, url: port_stocks_sell_order_path, method: :post, html: { class: "form-inline" } do |f| %>
  <% @buy_port_stocks.each do |port_stock| %>
    <%= f.simple_fields_for :closed_positions, @cp, html: { class: "form-inline" } do |c| %>
      ...

While I will obviously take the wealth and fame associated with the correct answer, it's only fair to admit that I didn't get you there alone.

Chiperific
  • 4,428
  • 3
  • 21
  • 41
  • 1
    Awesome. Thanks for such a comprehensive answer and for the intellectual honesty. You are a gentleman and a scholar sir! – marcamillion Jun 28 '18 at 21:49