1

I am trying to allow a user to input two different things in two different drop down menus from the same form and it will store an integer into a review table.

I want the user to be able to select model_name in one drop down and manufacturer in another drop down. The result will store a bat_id integer into the form. (Telling you which bat the user is selecting)

I have seen a couple questions about date & time but they store the values directly in the model. I am trying to store an integer - bat_id so that the bat_id will directly link the review model to the bat model.

Examples I have found that are close:

My form now:

<%= form_for(@review) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field" align= "center">
    <h3>Select Brand</h3>
    <%= f.collection_select :manufacturer_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
    <h3>Select Bat</h3>
    <%= f.grouped_collection_select :bat_id, Manufacturer.all, :bats, :manufacturer, :id, :model_year_and_name, include_blank: true %>
    <h3>What do you like about this bat?</h3>
    <%= f.text_area :pros, placeholder: "Enter what you like..." %>
    <h3>What do you not like about this bat?</h3>
    <%= f.text_area :cons, placeholder: "Enter what you don't like..." %></br>
  </div>
  <div align="center">
  <%= f.submit "Add Review", class: "btn btn-large btn-info" %>
  </div>
<% end %>

I am submitting to the review table and trying to submit both of these to the bat_id attribute.

<h3>Select Brand</h3>
<%= f.collection_select :manufacturer_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
<h3>Select Bat</h3>
<%= f.grouped_collection_select :bat_id, Manufacturer.all, :bats, :manufacturer, :id, :model_year_and_name, include_blank: true %>

In my bat model I have: has_many :reviews & In my reviews model I have: belongs_to :bat

UPDATE: Is it possible to use a hidden field with the combination of javascript and my two inputs to determine my one output bat_id?

Update I changed my dropdown code to what works so that I enter in manufacturer_id & bat_id when both are selected. However I still think there is a way to store one value in my review model. I am using javascript very similiar to this

Community
  • 1
  • 1
Daniel
  • 2,950
  • 2
  • 25
  • 45

2 Answers2

0

From a UI perspective this seems broken... users will be able to associate any model year & name with any manufacturer, even if that manufacturer did not produce that model year & name.

Assuming you will introduce some javascript to handle that, from a rails perspective you will get undefined behavior with two :bat_id fields in the same form. I think you need this:

<h3>Select Brand</h3>
<%= f.collection_select :manufacturer_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
<h3>Select Bat</h3>
<%= f.collection_select :bat_id, Bat.all, :id, :model_year_and_name, include_blank: true %>

Alternatively you can just create one dropdown containing a composite field, like this:

<h3>Select Bat</h3>
<%= f.collection_select :bat_id, Bat.all.sort {|a, b| a.manufacturer_model_year_and_name <=> b.manufacturer_model_year_and_name}, :id, :manufacturer_model_year_and_name, include_blank: true %>

and then in your Bat model introduce something like this:

def manufacturer_model_year_and_name
  "#{self.manufacturer.name}: #{self.model_year_and_name}"
end
Avi Tevet
  • 778
  • 1
  • 7
  • 13
  • Yes I am going to use javascript to show only the bats for that manufacturer once the manufacturer is selected. -This would get eliminate the broken UI problem correct? For solution #1 you provided- its redundant and going against the rails DRY principle by storing the `manufacturer_id` twice. ( I already store the `manufacturer_id` in the bat model) I could do it again but my goal was to use DRY. For solution #2 it does work, but I will be 100-200 bats and I do not want to repeat the manufacturer's name for 30 bats in a form on the UI side. – Daniel Feb 11 '14 at 20:08
  • I am unsure what this portion means- Can you explain better? `Bat.all.sort {|a, b| a.manufacturer_model_year_and_name <=> b.manufacturer_model_year_and_name}` My guess is all of the fields from the bat model are sorted in an array using a.manufacturer.... and b.manufacturer... which are attributes defined in the model. What does `|a,b|` & `<=>` mean? – Daniel Feb 11 '14 at 20:11
  • Yes, if you are using javascript to show only the bats for the selected manufacturer then that solves the broken UI problem. – Avi Tevet Feb 12 '14 at 09:40
  • `Bat.all.sort {|a, b| a.manufacturer_model_year_and_name <=> b.manufacturer_model_year_and_name}` sorts by the field 'manufacturer_model_year_and_name' attribute. `|a, b|` gives the two items that will be compared during any particular sort comparison. `<=>` is the awesomely named spaceship operator. For `x <=> y`, it returns -1 when x < y, 0 when x == y, and 1 when x > y. http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-sort, http://stackoverflow.com/questions/827649/what-is-the-ruby-spaceship-operator. You can use the sort {|a, b| ...} form of sort to sort by anything. – Avi Tevet Feb 12 '14 at 09:52
  • OK, last comment for now! :) WRT DRY, you need to give the collection_select for manufacturer a name, and it can't be :bat_id, because you are already giving the collection_select for bat that name. The most obvious choice is manufacturer_id, because after all, you are selecting a manufacturer_id. Your best option for not storing this attribute on the Review model is to modify your Review model to absorb/reject it: http://stackoverflow.com/questions/4128213/rails-ignoring-non-existant-attributes-passed-to-create – Avi Tevet Feb 12 '14 at 09:59
  • I could do that but that is more of a hacky way to accomplish my goal. I would rather link my tables by using the foreign key from the bat table in the manufacturer table. – Daniel Feb 12 '14 at 19:35
0

As discussed in your other answer, you shouldn't need to store the manufacturer_id on your review model.

I would recommend creating a Manufacturer select that isn't accessed in your Review model, but is simply used to filter the list of bats on the form.

The best way to do this is probably to add some custom data attributes to the Bat select.

<%= collection_select :manufacturer, :manufacturer_id, Manufacturer.all, :id, :manufacturer %>
<%= f.select :bat_id, Bat.all.map{ |b| [b.model_year_and_name, b.id, {'data-manufacturer' => b.manufacturer_id}] } %>

Then use some javascript to filter the Bat select when the Manufacturer select is changed.

Unfortunately you cannot just set display: none to an option element to hide it. This does not hide the option in many browsers. So the best method is to use a bit of jQuery to clone the original select every time the manufacturer select is changed, and remove any option that isn't associated with the selected manufacturer. Like so:

// rename the original select and hide it
$('#bat_id').attr('id', 'bat_id_original').hide();

$('#manufacturer_id').on('change', function() {
    $('#bat_id').remove(); // remove any bat_id selects
    $bat = $('#bat_id_original')
        .clone() // clone the original
        .attr('id', 'bat_id') // change the ID to the proper id
        .insertAfter('#bat_id_original') // place it
        .show()  // show it
        .find(':not(option[data-manufacturer="' + $(this).val() + '"])')
            .remove(); // find all options by other manufacturers and remove them
});

You might need to change a few things to get this to work in your installation, but you can view a static demo on jsFiddle here: http://jsfiddle.net/JL6M5/

You will probably need to reject the manufacturer_id field on form submit, avitevet already pointed out this answer which should help there: Rails: Ignoring non-existant attributes passed to create()

Community
  • 1
  • 1
Christian
  • 19,605
  • 3
  • 54
  • 70
  • Can I reject the `manufacturer_id` field using javascript or node.js? I would rather use a hacky language to do it instead of using RoR. – Daniel Feb 16 '14 at 22:17
  • Have you tried to see if it works first? You may not even need to reject the `manufacturer_id`; I haven't tested what happens when you submit a form with a non-existent attribute. It may work fine. Otherwise, you could override the form submit using javascript and remove the field before sending data to the server (you'll want to search up submitting a form with javascript). – Christian Feb 17 '14 at 00:38
  • @ravensfan55222 OK so I decided to test this out on the rails app I built yesterday, check it out here: http://secret-bayou-5954.herokuapp.com/reviews/new. You don't actually need to reject the `manufacturer_id` at all, but you have to build the select a slightly different way (I have edited my post to included the updated code, but I've also pushed the entire code to my repo at https://github.com/levymetal/stackoverflow-bats so you can view the working code there) – Christian Feb 17 '14 at 01:03
  • No I have not tried it yet but I will work on that now. ---If I use the new code you changed in the first code block, it says "undefined method `merge' for :manufacturer:Symbol". I used javascript from https://github.com/railscasts/088-dynamic-select-menus-revised/blob/master/store-after/app/assets/javascripts/people.js.coffee and have gotten it to work. It looks very similar to the JS you provided. --What is the slightly different way? I am unsure what you are talking about. – Daniel Feb 17 '14 at 02:43
  • Originally I used `f.collection_select`, but that can't be used if the the Manufacturer doesn't exist on the Review model (which as we discussed, it shouldn't). Try using `:manufacturer.manufacturer` at the end instead of just `:manufacturer`. – Christian Feb 17 '14 at 02:53
  • It still says the same thing. I think that it is talking about `manufacturer` in `collection_select :manufacturer, :manufacturer_id`. – Daniel Feb 17 '14 at 03:03
  • Again, unfortunately I can't tell you what's going wrong because the code works for me. Here is the corresponding view in my working app: https://github.com/levymetal/stackoverflow-bats/blob/master/app/views/reviews/_form.html.erb. Perhaps you should change the `manufacturer` field on your `manufacturer` model to `name` instead, because that's what it really is. `manufacturer.manufacturer` isn't following best practices, and now you have a model and an attribute with the same symbol, which _could_ be the cause of your problem. – Christian Feb 17 '14 at 03:30
  • Not sure why I never thought of this, but I could use javascript to accept the first dropdown selection (not store anything in my review model) then populate the second dropdown based on the first dropdown. I would only store the second drop down selection in bat_id. The bat table links to the manufacturer table so it would work. I'm not sure if you were trying to tell me that but that makes this entire question unnecessary. --Would you recommend I change my column in my Manufacturer table named `manufacturer` to `name`? – Daniel Feb 17 '14 at 22:31
  • @ravensfan55222 that's exactly what the code in my repo does; check out https://github.com/levymetal/stackoverflow-bats/blob/master/app/assets/javascripts/reviews.js.coffee and https://github.com/levymetal/stackoverflow-bats/blob/master/app/views/reviews/_form.html.erb. You'll also notice that the review model does not have a `manufacturer_id` or anything related to the manufacturer. Also, yes, if I was you I would change the column name to `name` instead of `manufacturer`, that way it makes your code more semantic. – Christian Feb 18 '14 at 02:50