Calling all Rails superheroes! I'm racking my brains with this one and can't find a solution anywhere online...I want to build year/make/model drop-downs to select a car from a table of 30,000+ different cars. The table is called car_variants. Here's a link to a similar thing on kbb.org
The Year is selected first which then populates the Make drop-down with those that were available in that year. This in turn populates the Model drop-down. All this filtering overcomes the dreadful problem of having the 30,000 car_variants in one unusable drop-down.
Unfortunately I don't have enough reputation to post my entity diagram, but essentially...
a car_make has many car_models
a car_model has many car_variants
a car_year has many car_variants
SIDE NOTE Since the car_variants table will be populated by importing an excel spreadsheet, I decided to join car_year to car_variants directly rather than saying a car_year has many car_models. That may or may not affect the challenge below.
The car_variants model ( a table of virtually every car since 1984) stores the car_year_id and the car_model_id. (car_make is obvious through car_model)
Now... along comes the user. They want to select their car from the car_variants table then add their car's nickname and license_plate. The user potentially has many cars so the car_variant_id's can't be stored in the user table. Instead we have a car table which contains the users cars.
a car_variant has many cars (think of a car as being like an instance of a car_variant)
a user has many cars
Ryan Bates did an excellent screencast about Dynamic Select Menus. In his example (Countries and States), he used grouped_collection_select and some javascript. That certainly appears to be heading in the right direction for this challenge too, but this is more complex because there are more has_many relationships than grouped_collection_select seems to be able to handle.
I have successfully coded the car form with 3 drop-downs for year, make and model...
<%= simple_form_for(#car) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<!-- select year to filter make dropdown -->
<p>Year</p>
<%= select_tag :car_year, options_for_select(CarYear.all.collect {|e| ["#{e.year}", e.id] }), :include_blank => true %>
<!-- Select make to filter model dropdown -->
<p>Make</p>
<%= select_tag :car_make, options_for_select(CarMake.all.collect {|e| ["#{e.name}", e.id] }), :include_blank => true %>
<!-- Select model -->
<p>Model (grouped collection select)</p>
<%= f.grouped_collection_select :car_variant_id, CarMake.order(:name), :car_models, :name, :id, :name, include_blank: true %>
<%= f.input :nickname, :hint => "If you've given your car a nickname enter it here, otherwise just leave it blank." %>
<%= f.input :plate %>
<%= f.hidden_field :user_id, :value => current_user.id %>
</div>
<div class="form-actions">
<%= f.button :submit, "Add Car" %>
</div>
<% end %>
...now how do I tie them all together so they filter one another then return the car_variant_id to be stored in the car_form.
After several late bleary nights, I'm out of ideas.
Related
I'm making an app where some activities are listed in a table called Fakultety (polish language, sorry), and participants on in another table called Uczestnicy.
I have a submit form where you can submit yourself to an activity, but I'm stuck on passing values to a DB. Firstly, I don't know how to tell to the database on which activity you want to be assigned to (I tried to change the submit button id to an activity id and then passing it into a database but don't know how to do this id: "#{#fakultet.id}" not working) and later I want to count how many people are assigned to field participants in a database Fakultety, but I don't want to pass all the data, just ID of the users from table called Uczestnicy. How to do it? I mean just to pass the ids to another table, and how to tell the database on which activity I want to be assigned to?
This is my form view:
<h1>Zapisujesz sie na fakultet</h1>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#participant, url: zapisy_path) do |f| %>
<p>Imię:</p>
<%= f.text_field :firstName, class: 'form-control' %>
<p>Nazwisko:</p>
<%= f.text_field :lastName, class: 'form-control' %>
<p>Grupa:</p>
<%= f.text_field :group, class: 'form-control' %>
<%= f.submit "Zapisz się", class: "btn btn-primary" id: "#{#fakultet.id}"%>
<% end %>
</div>
</div>
</div>
Does anybody understand me and can help me?
Rails provides form collection helpers that make it really easy to assign associations:
# I'm going to just do this in english
<%= form_for(#activity) do |f| %>
<%= f.collection_select(:participant_ids, Partipicant.all, :id, :name, prompt: true, multiple: :new) %>
# ...
<% end %>
Then whitelist the attribute as an array:
params.require(:activity).permit(:foo, :bar, participants_ids: [])
Thats all you actually need to assign childen to to a parent resource. This is done as a part of the normal create/update actions of the parent resource.
You don't actually need the form for the child records unless you actually want to be creating the record. In that case you can setup a nested resource or if you want to create/edit multiple nested records at the same time as the parent record you can use nested attributes.
First you should rename your models and tables, to English, it's a really bad pattern to use your language, in English it is easier to understand by other devs.
According to the problem, probably what you are looking for is hidden_field
<%= f.hidden_field :fakultet_id, :value => #fakultet.id %>
and if you want to query Fakultety that have user assigned, you can select Fakultety where participant_id is not nil
Fakultety.where.not(participant_id: nil)
I'm struggling to create a dynamic dropdown box arrangement. Thanks to StackExchange Ruby community, I was able to create the first dropdown box. What I'd like to do, is have the user select a group of banks in a particular state, but keep getting the following error message from my f.grouped_collection_select form: undefined method `map' for "MD":String. I looked at ActionView::Helpers::Forms and this video, but nothing seems to work. Any help you could provide would be greatly appreciated. I think I also have to add jquery code too? Here is my current view code:
<%= form_for #boli do |f| %>
<%= f.label :state %>
<%= f.collection_select :state, (Boli.order(:state).select("DISTINCT ON (state) id, state")), :id, :state, include_blank: true %>
<div>
<%= f.label :bank %>
<%= f.grouped_collection_select :bank, Boli.order(:bank), :state, :name, :id, :name, include_blank: true %>
</div>
<% end %>
You need to group banks by state. This implies that there is either a one-to-many (one state to many banks) or a many-to-many relationship between them.
The one-to-many relationship implies Bank is a model that either has state as an attribute or references another model State.
The many-to-many relationship implies that both Bank and State are models joined by another table.
With both bank and state as attributes of another model it's unfeasible to model the relationship between them.
In order to use grouped_collection_select you should have both as models.
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:
How do ruby on rails multi parameter attributes really work (datetime_select)
Rails multiple fields to one model attribute
Using multiple input fields for one attribute
Rails Update Single Attribute with Multiple Fields
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
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
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()
I have a two different fields in my form like below.
<div class="field">
<%= f.label :Master_Survey %><br/>
<%= f.select :master_survey, Condition::MasterSurvey.all.map{|e| [e.Master_Survey_Code]}, { :prompt => 'Please Select' } %>
</div>
<div class="field">
<%= f.label :Element_Code %><br/>
<%= f.select :Element_Code, Condition::Element.all.map{|e| [e.Element, e.Element_Code]}, { :prompt => 'Please Select' } %>
</div>
I want the second field should disabled unless the first got selected. And The Second field Element code should change the value depend upon the First field Master Survey selected. I have a Master Survey Code in the Elements Table.
If these two models are related via has_many/belongs_to or something similar, the best bet is going to use the grouped_collection_select method for your drop downs. This will organize your drop down into a tabbed-list (using optgroup), but then doing some jQuery magic to make the 2 drop down selections dynamic and chained!
Ryan Bates has a RailsCast that explains in great detail exactly how to do this. If you don't have a pro-subscription to RailsCasts, I highly recommend getting one so you can easily find out how to do things like this :)
Hope this points you in the right direction!
I have two models: Company and Person
class Person < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :people
accepts_nested_attributes_for :people, :allow_destroy => true, :reject_if => proc {|attrs| attrs.all? {|k,v| v.blank? } }
end
And my HTML form partial for new and edit actions looks like this:
<% form_for(#company) do |company_f| %>
<p>
<b>Name</b><br />
<%= company_f.text_field :name %>
</p>
<ul>
<%= render :partial => 'person_fields', :collection => #company.people, :locals => {:company_f => company_f} %>
<%= link_to_add_fields(:people, company_f) %>
</ul>
<p>
<%= company_f.submit "Submit" %>
</p>
<% end %>
where "_person_fields" partial looks like this:
<li>
<% company_f.fields_for :people, person_fields do |person_f| %>
<%= person_f.label :name %>
<%= person_f.text_field :name %>
<% end %>
</li>
At the moment, if I typed in person_f.text_fiel :name the name of the person, and hit save, a new Person model with that name gets created. Not what I want at all, I already HAVE that person's Person model in the database, I rather want to ASSOCIATE this person to this company.
Another thing is that I wouldn't mind using the name for human-friendly identification of the person rather than id like this for the "_person_fields" partial
<li>
<% company_f.fields_for :people, person_fields do |person_f| %>
<%= person_f.label :id %>
<%= person_f.text_field :id %>
<% end %>
</li>
this by the way, doesn't work either. when I hit submit, nothing happens. nothing gets saved or changed or anything.
So I thought, just for the sake of experiment, say I did use id's for identification for a Person model, (so that I don't have to go in to autocomplete with a hidden id field which I am using for another project. I hate it). All I want is: go to a new/edit Company page, there's a bunch of textfields for me to type in ids of people, and save and then these people are then associated with the company. I mean, it's exactly like
people = Person.find(1,2,3)
#=>["romeo","juliet","henry"]
company = Company.first
#=>["Shakespeare Co."]
company.people<<people
company.people
#=>["romeo","juliet","henry"]
And it'd be best if I didn't have to use select menus because eventually if the project takes off and I have a thousand people, that's too big for any select menu. I know then I will have to use autocomplete + hidden id field that gets set when a person's name is chosen.
Thanks!!
accepts_nested_attributes_for :people defines people_attributes and people_attributes= methods in your Company model. Those two methods are used when you have fields_for :people in form. As stated in documentation (read whole page, not only method definition):
Nested attributes allow you to save attributes on associated records through the parent.
It doesn't work to associate two objects, that can be done without it, you still have people method in Company.
Formtastic for example uses select with multiple attribute (you can check more than one option with ctrl) or list of check_boxes (easier for normal user, don't have to touch keyboard).
If you want to use autocomplete, it could be done, but you need to append your ids to people[] array (don't remember format right now, I'll check later. You can check its format when you create attribute in your form for :people I think)
Edit:
I think I know hot to do it simple way (without autocomplete, simple html only).
In your companies/edit (or new) view place:
<p>
<%= company_f.label :people %>
<%= company_f.collection_select :person_ids, Person.all, :id, :name, {}, {:multiple => true} %>
</p>
That will allow you to select multiple people to company (with Ctrl). Params generated by this part transfered to your controller should now look like:
"company"=>{"name"=>"CompanyA", "person_ids"=>["1", "3"]}, "commit"=>"Update", "id"=>"4"
If you want to show people with checkboxes I managed to get it with this (I copied part of markup from formtastic):
<ul>
<% Person.all.each do |person| %>
<li>
<%= check_box :person, :id, {:name => "company[person_ids][]", :checked => #company.people.include?(person) }, person.id, nil %>
<%= person.name %>
</li>
<% end %>
</ul>
I had no time today to check how to do it with hidden fields, so you can use autocomplete, but I believe that it should be similar to checkboxes - you need to use something which adds <input type="hidden" name="company[person_ids][]" value="#{person.id}"> for every autocompleted person, and also need to create initial list of people already in company (YOU OVERRIDE THE LIST, not add to list).
If you want field to add or remove (separate fields, sorry) people from company, then this should work:
in Company model:
def add_people=(people_ids)
ids = people_ids.split(/,/).map(&:to_i)
person_ids += ids if ids
end
def add_people
""
end
def remove_people=(people_ids)
ids = people_ids.split(/,/).map(&:to_i)
person_ids -= ids if ids
end
def remove_people
""
end
and in your new/edit view:
<p>
<%= f.label :add_people %><br />
<%= f.text_field :add_people %>
</p>
<p>
<%= f.label :remove_people %><br />
<%= f.text_field :remove_people %>
</p>
Now all you need to do is to find JavaScript for auto-complete, connect it to ids and names of all people (probably PeopleController#index, :format => :json should be good) and tell it to fill those text_fields (can be hidden fields if you would use autocomplete).
This approach should work, because you define virtual attributes in company model, and by assigning to them string in format "1, 2, 6", you add/remove those ids from your collection of associated people