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:
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:
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.
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:
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?
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.