12

Is there a way to create multiple model on the same page that are not nested inside another?

For instance, I would like to have a form where you can create users. It has two simple fields, firstname and last name, displayed on the same line. I would like to be able to add a link "Add a new user" which would create (using javascript) an identical line without post-back and allow me to create two users on the same page submit.

How can I achieve that using rails?

Dominic Goulet
  • 7,983
  • 7
  • 28
  • 56

2 Answers2

9

Adding fields and keep only one form, one submit button:

= form_tag(url: create_user_path, remote: true) do
  %table
    %tr
      %td= text_field_tag 'user[][first_name]'
      %td= text_field_tag 'user[][last_name]'

    %tr.actions
      %td= submit_tag 'Save'
      %td= button_tag 'Add new user form', id: 'add_user_form'

    %tr.new_user_row.hidden # hidden class matches the css rule: {display:none;}
      %td= text_field_tag "user[][first_name]"
      %td= text_field_tag "user[][last_name]"

:javascript # jQuery
  $('#add_user_form').bind('click', function(e) {
    var row = $('tr.new_user_row').clone().removeClass('hidden new_user_row');
    $('tr.actions').before(row); # will append the <tr> before the actions
  });

In UsersController:

def create
  params[:user].each do |attr|
    User.create(attr)
  end
end

The row tr.new_user_row.hidden serves the purpose of template for a new line: by clicking on the button #add_user_form, the JS code will select the template row, clone it and add this new row with empty inputs as the last visible row of the table.

MrYoshiji
  • 54,334
  • 13
  • 124
  • 117
  • That includes one submit button per form. I would like to have one submit button to create multiple users. – Dominic Goulet Jun 04 '13 at 14:38
  • Well, add an HTML class on the forms, take off the submit button of the hidden form and bind the submit button to submit all forms that match the class you gave. Do you see what I mean? – MrYoshiji Jun 04 '13 at 14:45
  • @DominicGoulet Okay you're from QC, French will be okay? Ajoutez une classe du genre `.user_form` sur chaque formulaire, et faites un bind sur le submit button afin qu'il fasse `$('.user_form').submit();` afin qu'un seul bouton puisse soumettre plusieurs formulaires. – MrYoshiji Jun 04 '13 at 14:47
  • So basically you are suggesting to post multiple forms asynchronously. That would work, but is it the rails way to achieve my goal? I was hoping for a way to post an array of users with my form and build a collection in the controller. – Dominic Goulet Jun 04 '13 at 15:02
  • Hmm you are right. You should use `fields_for 'new_user'` http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for – MrYoshiji Jun 04 '13 at 15:12
  • That works when you are using nested models, for instance creating tasks in a project. But I do not have a parent container, and I am wondering how to use the fields_for in that scenario. If I want to create 5 users, should there be 5 forms in the page that are submitted like you mentioned, or should there be one form with 5 users in it passed as an array and then a collection is built in the controller? – Dominic Goulet Jun 04 '13 at 15:18
  • Lot better solution, but still the id replacement feels a bit clunky. I'm pretty sure there is something more we can do with rails. But still a very good step in the right direction ! ;-) Thanks – Dominic Goulet Jun 04 '13 at 15:49
  • I don't think that could be cleaner, because here you don't have an explicit limit of users you could create. It means each new row for user inputs need to have different names. Take a look at this also: http://stackoverflow.com/questions/4641565/rails-3-submit-a-form-with-multiple-records As you can see in the comments there is a problem about "adding a new row" – MrYoshiji Jun 04 '13 at 15:56
  • 1
    Thanks @MrYoshiji for this solution. It's the cleanest one I could find. I still think that the replace part is clunky, but there is no alternative yet. I was thinking about using a partial that accepts an id parameter and getting it using asynchronous request, but that would involve a get action to the server. Your solution simply uses a local template, which is faster. – Dominic Goulet Jun 05 '13 at 12:36
  • 1
    I have played with this a bit and found a cleaner solution with no replace! I updated your answer. In rails doc : "If the parameter name contains an empty set of square brackets [] then they will be accumulated in an array." – Dominic Goulet Jun 07 '13 at 15:05
  • I'm thinking about a way to have a template that will be used as the initial form and the new_user_row, in case of bigger forms. DRY it up a bit ;-) – Dominic Goulet Jun 07 '13 at 15:09
  • Any news in 2016? Is this a rellay good Rails way to accomplish this, gentlemen? –  Feb 17 '16 at 12:00
  • @JohnSam It is a JS solution, not based on Ruby nor Rails. I think it is a very good solution since it is just copy-pasting a hidden row structured like the other rows and their input(s), and not doing any AJAX calls to the server. – MrYoshiji Feb 17 '16 at 14:26
  • I'm trying to implement this in Rails 4, but I receive this error: "ActiveModel::ForbiddenAttributesError - ActiveModel::ForbiddenAttributesError:" –  Feb 17 '16 at 14:31
  • I think it's an error with `strong_parameter`. How to do? –  Feb 17 '16 at 14:31
  • 1
    @JohnSam this answer might help you, and yes, it seems to be linked with strong_parameters http://stackoverflow.com/questions/17335329/activemodelforbiddenattributeserror-when-creating-new-user (you must permit an array for the `params[:user]` key (or `params[:users]`, depending on what name you used) – MrYoshiji Feb 17 '16 at 16:02
  • And then a curiosity, instead of save every each per time, how to save one time with a big INSERT query? –  Feb 17 '16 at 20:23
  • 1
    @JohnSam You would have to compile a big SQL insert statement manually, bypassing the Rails' tools (ActiveRecord). I do not recommend this, since doing an instert for each is not bad, and doing a SQL query manually can lead to various problems (SQL injections, wrong attribute assignment on creation, syntax errors, etc). – MrYoshiji Feb 18 '16 at 13:33
3

Create multiple records in one form with one model

Here's users_controller.rb

 def new
 end
 def create_multiple
    params[:users].each do |user|
     user = User.create(user)
    end
    redirect_to users_url
  end

Here's new.html.erb

<%= form_tag '/users/create_multiple' do %>

  <%= render 'user_fields' %>
  <div class="actions">
    <%= submit_tag %>
  </div>
<% end %>

Here's _user_fields.html.erb

<script type="text/javascript">
$(function() {
        var scntDiv = $('#addusers');
        var i = $('#addusers div').size() + 1;

        $('#addfields').on('click', function() {
                $('<div class="field"><h2>User ' + i +'</h2><input id="users__firstname' + i +'" name="users[][firstname]" placeholder="first name" type="text" /><input id="users__lastname' + i +'" name="users[][lastname]" placeholder="last name" type="text" /></div>').appendTo(scntDiv);
                i++;
                return false;
        });


});

</script>
<div id="addusers">
<div class="field">
 <h2>User 1</h2>
 <%= text_field_tag "users[][firstname]", nil, :placeholder => "first name" %>
 <%= text_field_tag "users[][lastname]", nil, :placeholder => "last name" %>
</div>
</div>

<a href="#" id="addfields">Add a New User</a><br/>

Result on Log

Started POST "/users/create_multiple" for 127.0.0.1 at 2013-06-05 00:40:07 +0700

Processing by UsersController#create_multiple as HTML
  Parameters: {"utf8"=>"V", "authenticity_token"=>"xOPM6PB1h6DMUEGS7fX9/eWs/e6dg
XKRj231ReviKFo=", "users"=>[{"firstname"=>"test1", "lastname"=>"last1"}, {"first
name"=>"test2", "lastname"=>"last2"}], "commit"=>"Save changes"}
  ←[1m←[36m (78.0ms)←[0m  ←[1mbegin transaction←[0m
  ←[1m←[35mSQL (49.0ms)←[0m  INSERT INTO "users" ("created_at", "firstname", "la
stname", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", Tue, 04 Jun 2013 17:
40:08 UTC +00:00], ["firstname", "test1"], ["lastname", "last1"], ["updated_at",
 Tue, 04 Jun 2013 17:40:08 UTC +00:00]]
  ←[1m←[36m (7.0ms)←[0m  ←[1mcommit transaction←[0m
  ←[1m←[35m (0.0ms)←[0m  begin transaction
  ←[1m←[36mSQL (3.0ms)←[0m  ←[1mINSERT INTO "users" ("created_at", "firstname",
"lastname", "updated_at") VALUES (?, ?, ?, ?)←[0m  [["created_at", Tue, 04 Jun 2
013 17:40:08 UTC +00:00], ["firstname", "test2"], ["lastname", "last2"], ["updat
ed_at", Tue, 04 Jun 2013 17:40:08 UTC +00:00]]
  ←[1m←[35m (5.0ms)←[0m  commit transaction
Redirected to http://localhost:3000/users
Completed 302 Found in 156ms (ActiveRecord: 142.0ms)

You can add validation code as per your wishes, here's how to pass form params for multiple records basic-structures

rails_id
  • 8,120
  • 4
  • 46
  • 84