0

Ruby 2.0.0, Rails 4.0.3

I have a _new partial. But, I render it with an instance that actually exists. This is necessary so that I can pass the instance ID around through JavaScript between the View and the Controller, since I cannot actually pass the instance itself. I simply call Class.first in the new method so that I have an existing instance to use through the process.

My problem is that the _new partial submit button recognizes that the instance already exists. This causes it to update instead of create. The button literally says update. When pressed, it routes to the update method. That is not what I want. I want the create method, where I'll create a new instance populated with the gathered parameters.

What to do? Am I wrong in just carrying a dummy instance to start the process? If so, what is the correct solution? If this is otherwise acceptable, how do I force the button to create instead of update?

All assistance and comments appreciated.

EDIT: I've tried variations on the button to try to force it to trigger to the new method. It continues to fire to update. My last failed effort is:

  <button type="submit" formaction="new_admin_car_path" class="btn btn-default btn btn-primary">Create Car</button>

...End Edit...

The form is:

<div class="span8">
  <% car_id = @car.id %>
  <%= simple_form_for [:admin, @car],
                      defaults: {label: false},
                      html: {id: 'new_admin_car', class: 'form-vertical', method: post},
                      wrapper: :vertical_form,
                      wrapper_mappings: {
                              check_boxes: :vertical_radio_and_checkboxes,
                              radio_buttons: :vertical_radio_and_checkboxes,
                              file: :vertical_file_input,
                              boolean: :vertical_boolean
                      } do |f| %>
      <%= f.input(:stock_number, {input_html: {form: 'new_admin_car', car: @car, value: nil}, autocomplete: :off, placeholder: 'Stock number?'}) %>
      <%= f.input(:ymm_year_id, {input_html: {form: 'new_admin_car', car_id: car_id, value: nil}, collection: YmmYear.all.order("year desc").collect{|c| [c.year, c.id]}, prompt: "Year?"}) %>
      <%= render partial: "makes", locals: {form: 'new_admin_car', car_id: car_id} %>
      <%= render partial: "models", locals: {form: 'new_admin_car', car_id: car_id} %>
      <%= f.association(:color, {input_html: {form: 'new_admin_car', value: nil}, autocomplete: :off, prompt: 'Color?'}) %>
      <div class="col-xs-6 col-sm-3">
        <br/>
      <input type="submit" form="new_admin_car" value="Create Car" class="btn btn-default btn btn-primary">
  <% end %>
  </div>
</div>

Makes partial:

<% unless car_id.blank? %>
    <% car = Car.find(car_id) %>
    <%# car.ymm_make_id = nil %>
    <%= simple_form_for [:admin, car],
                        defaults: {label: false},
                        remote: true do |f| %>
        <% makes ||= "" %>
        <% make = "" %>
        <% make = car.make_id if car.class == Car and Car.exists?(car.id) %>
        <% if !makes.blank? %>
            <%= f.input :ymm_make_id, {input_html: {form: form, car: car, car_id: car.id, value: make}, collection: makes.collect { |s| [s.make, s.id] }, prompt: "Make?"} %>
        <% else %>
            <%= f.input :ymm_make_id, {input_html: {form: form, car: car, car_id: car.id, value: make}, collection: [], prompt: "Make?"} %>
        <% end %>
    <% end %>
<% end %>

Controller Car Method New where clear just nils all fields:

  def new
    @car = Car.first
    @car.clear
  end

Rendered form: enter image description here

JavaScript for the form is:

// when the #year field changes
$("#car_ymm_year_id").change(function () {
    // make a GET call and replace the content
    // First select identifies what has been selected, or fired
    var year = $('select#car_ymm_year_id :selected').val();
    // Pull the variables from the input_html tag
    var form = $('select#car_ymm_year_id').attr("form");
    var car_id  = $('select#car_ymm_year_id').attr("car_id");
    // Routes to the controller action
    $.post('/admin/cars/make_list/',
        {
            form: form,
            year: year,
            car_id: car_id
        },
        function (data) {
            $("#car_ymm_make_id").html(data);
        });
    return false;
});

Controller method:

  def make_list
    makes = YmmMake.makes(params[:year])
    #@car = Car.find(params[:car_id])
    render partial: "makes", locals: {car_id: params[:car_id], form: params[:form], makes: makes}
  end
Richard_G
  • 4,700
  • 3
  • 42
  • 78
  • Can you elaborate a bit more about what you mean by passing the instance ID around through JavaScript between the view and the controller? We might be able to find a better solution there and avoid the whole thing in the first place. Using an existing instance, then forcing the form helpers to treat it like a new instance with a crowbar does indeed sound like a bad idea. – janfoeh Sep 13 '14 at 13:55
  • @janfoeh I was trying to keep it simple but that isn't working so... I've updated the question to include all related partials, forms and methods. The issue is that I have a series of dependent selects. When I pick a year for the car, it returns all the "makes" or manufacturers that built the car in that year. And so on... To do this, I use an instance. Please refer to the code and let me know if you have further questions. Thanks. – Richard_G Sep 13 '14 at 14:32
  • I think your code has a few misconceptions and outright errors. I'll create a blank, clean version of what you're trying to achieve and try to clear those up along the way. – janfoeh Sep 13 '14 at 15:44

3 Answers3

1

If I understand this correctly, you want to achieve the following:

Display a form for creating new car models. In this form, the user enters a year; the system then loads all makes for that year from the server and presents a select drop down for the user to choose a make from.

The Car model belongs_to ymm_make, so we include the selected ymm_make_id when the form is submitted.

Here is how I would solve this. I will use the standard Rails form helpers, so that we have one less abstraction layer to worry about.

The form (with Car.new):

<%= form_for [:admin, Car.new] do |f| %>
  <%= f.text_field :stock_number, autocomplete: "off", placeholder: "Stock number?" %>
  <%= text_field_tag :year_search, nil, placeholder: "Year" %>
  <%= f.select :ymm_make_id %>

  <!-- skipping models and colors here for the sake of brevity -->

  <%= f.submit "Create" %>
<% end %>

For the year search field, I use text_field_tag instead of f.text_field, because I don't want the search field value to be submitted as a part of the car when the whole form is submitted. I leave the dropdown field empty for now - we will populate that through Javascript and JSON.

For the list of makes, I'll make a resource controller that returns JSON:

class YmmMakesController < ApplicationController
  respond_to :json

  def index
    @makes = YmmMake.makes(params[:year])

    respond_with @makes
  end
end

Don't forget a route.rb entry for this controller, e.g.

namespace :admin do
  resources :ymm_makes, only: :index
end

We'll make <select> options out of our JSON in Javascript:

$("input[name=year_search]").change(function () {

    // send a GET request to /admin/ymm_makes with the 'year' parameter
    // set to the value of the year_search text field
    $.getJSON( "/admin/ymm_makes", {year: $(this).val()}, function(data) {
      var options_html = [];

      // iterate over the JSON that we received back; each entry is one 'ymm_make'
      // in JSON form
      $.each( data, function( index, make ) {
        // make a new <option> tag for each make and push it into the options_html array
        // I assume here that YmmMake has an attribute called 'name' you want to display
        options_html.push( "<option value='" + make.id + "'>" + make.name + "</option>" );
      });

      // put all our generated <options> tags into the <select> tag
      $('select#car_ymm_make_id').html( options_html.join('') );
    });

});

With all this in place, you should have a working form for creating new cars that are associated with a YmmMake model object.

A few observations about your existing code:

Nested forms

<%= simple_form_for [:admin, @car] do |f| %>
  <%= render partial: "makes", locals: {form: 'new_admin_car', car_id: car_id} %>
<% end %>

If I see this correctly, your 'makes' partial also contains a form, so you are creating a <form> nested in a <form>. HTML does not allow that.

Wrong closing tag order

      <div class="col-xs-6 col-sm-3">
        <br/>
      <input type="submit" form="new_admin_car" value="Create Car" class="btn btn-default btn btn-primary">
  <% end %>
  </div>

The closing </div> must come before the <% end %>. If you build invalid HTML, you risk strange visual behaviour and Javascript errors.

Redundant arguments

<%= simple_form_for [:admin, @car],
                      html: {id: 'new_admin_car', class: 'form-vertical', method: post},
                      do |f| %>

"id" and "method" should be the default values. Leaving them out makes the code easier to read.

Invalid <input> attributes through input_html

<%= f.input(:stock_number, {input_html: {form: 'new_admin_car', car: @car, value: nil}, autocomplete: :off, placeholder: 'Stock number?'}) %>

I'm not familiar with simple_form, but from what I see, input_html is used to add attributes to the input element. Lines like the above would thus produce a text input with an invalid car attribute. The form attribute should not be necessary anymore as soon as you remove the nested form.

Wrong HTTP method

$.post('/admin/cars/make_list/'

You load your makes through a POST AJAX request. For requests that only return data, but do not change anything, GET is usually more appropriate.

janfoeh
  • 10,243
  • 2
  • 31
  • 56
  • Okay, looks interesting and I am trying to implement it. However, my application is based on Simple Form. Advice on implementing this in it? – Richard_G Sep 14 '14 at 15:56
  • I would recommend to start with the built-in form helpers as outlined above. As soon as you have a working baseline, you can refactor it to use Simple Form until it works again. Both methods are not mutually exclusive and can be used in parallel just fine. – janfoeh Sep 14 '14 at 18:18
  • There were several issues in implementing this recommendation but, in the end, it is a vast improvement over the old code. Thanks. – Richard_G Sep 18 '14 at 09:53
  • @R_G Thanks for the feedback! If you have a minute or two to spare, I would love to hear about the issues you ran into. – janfoeh Sep 18 '14 at 11:28
  • The concept and design worked. There were just a lot of details involved in implementation. The Year had to be a collection. The collections needed prompts. Things like that. Still working on getting default values to work. Please see this URL and tell me if you can answer it? Thanks: http://stackoverflow.com/questions/25951405/rails-form-wont-select-default-value – Richard_G Sep 21 '14 at 13:53
0

In your form you can specify the path the submit button should go to:

<%= simple_form_for [:admin, @car], url: create_action_path

...
Anthony
  • 15,435
  • 4
  • 39
  • 69
0

I think your answer is fairly simple, to create instead of update, just clear the model's id.
Example: @car.id = nil

Korkey
  • 21
  • 1
  • 5