raisinrising
raisinrising

Reputation: 268

How to create non-nested multiple records of the same model using one form in Rails?

I looked at related questions on Stack Overflow, but I am at a loss trying to figure this one out.

To elucidate,

  1. There are three models: User, A and B.
  2. A contains, let's say, 3 records with the fields p1_name, p1_score, p2_name and p2_score for each record. So, A looks like

-----------------------------------------------------------
id | p1_name | p1_score | p2_name | p2_score
id | p1_name | p1_score | p2_name | p2_score
id | p1_name | p1_score | p2_name | p2_score
-----------------------------------------------------------

  1. B is the model where the the User submits his guesses for what p1_name and p2_name from A scored. So, B looks like:

---------------------------------------------------------------------------------
id | user | a_id | p1_name | p1_score_guess | p2_name | p2_score_guess
id | user | a_id | p1_name | p1_score_guess | p2_name | p2_score_guess
id | user | a_id | p1_name | p1_score_guess | p2_name | p2_score_guess
---------------------------------------------------------------------------------

  1. B belongs_to A as each record in B is uniquely identified by user and a_id put together.

I need to make a form for model B, handled by the new action for its controller, using the view views/B/new.html.erb.

This needs to be a single form that shows 3 rows each containing:

Upon submission using one submit button, the above data for each of the 3 records needs to be submitted to the controller, and multiple records of B be created.

I have not encountered this kind of requirement in Rails 4.2.5 before, and am not sure how to go about it. I have been trying to do this since two days, first using simple_form, which did not seem to support this (or I am not able to make one), and then the Rails Forms.

It's become tricky for me because I have to show the p1_name and p2_name from model A in the view under the form as well as use it also, to populate model B. Bs controller needs to take in both p1_name, p2_name and p1_score_guess, p2_score_guess. Since a_id is different for each row, that needs to be taken in as well (further making it impossible for me to figure this one out).

Upvotes: 2

Views: 664

Answers (1)

bpieck
bpieck

Reputation: 288

how about:

<%= form_for :bs, url: bs_path do |f| %>
  <% @as.each do |a| %>
    <%= f.fields_for "[#{a.id}]", B.new do |afields| %>
      <%= a.p1_name %>
      <%= afields.text_field :p1_score_guess %>
      <%= a.p2_name %>
      <%= afields.text_field :p2_score_guess %>
    <% end -%>
  <% end -%>
  <%= f.submit 'save' %>
<% end -%>

And in Controller you iterate through params[:a_s]

def create
  bs_params.each do |a_id, b_attributes|
    B.create b_attributes.merge(a_id: a_id, user: current_user)
  end
end

with

def bs_params
  params.require(:bs).permit(allowed_a_s)
end

def allowed_a_s
  Hash[a_ids.map {|id| [id.to_s, [:p1_score_guess, :p2_score_guess]]}]
end

def a_ids
  A.pluck(:id)
end

Just to show you my testcase - I assigned for @as:

@as = [A.new(id: 1, p1_name: 'p11', p2_name: 'p12'), A.new(id: 2, p1_name: 'p21', p2_name: 'p22'), A.new(id: 3, p1_name: 'p31', p2_name: 'p32')]

And set as a_ids static:

def a_ids
  %w[1 2 3]
end

and got on a debugger for bs_params with inputs 1, 2, 3, 4, 5, 6 for guesses:

=> {
  "1"=>{"p1_score_guess"=>"1", "p2_score_guess"=>"2"},
  "2"=>{"p1_score_guess"=>"3", "p2_score_guess"=>"4"},
  "3"=>{"p1_score_guess"=>"5", "p2_score_guess"=>"6"}
}

Update: Since above code does not check validations, I try again (this time not tested - sorry):

<%= form_for :bs, url: bs_path do |f| %>
  <% @bs.each do |a, b| %>
    <%= f.fields_for "[#{a.id}]", b do |afields| %>
      <%= a.p1_name %>
      <%= afields.text_field :p1_score_guess %>
      <%= a.p2_name %>
      <%= afields.text_field :p2_score_guess %>
    <% end -%>
  <% end -%>
  <%= f.submit 'save' %>
<% end -%>

And in controller do:

def new
  @bs = Hash[a_ids.map { |a_id| [a_id, B.new] }]
end

def create
  @bs = {}
  bs_params.each do |a_id, b_attributes|
    @bs[a_id] = B.new b_attributes.merge(a_id: a_id, user: current_user)
  end
  @bs.values.each(&:valid?) # to ensure, all errors are assigned - and not finding one will stop to check for any other
  if @bs.values.all?(&:valid?)
    @bs.values.each &:save
    redirect_to root_path
  else
    render :new
  end
end

and bs_params, allowod_a_s and a_ids as above

Upvotes: 2

Related Questions