How do I implement text fields with autocomplete for habtm fields? - ruby-on-rails

I tried the example from Rails Cookbook and managed to get it to work. However the text_field_with_auto_complete works only for one value.
class Expense < ActiveRecord::Base
has_and_belongs_to_many :categories
end
In the New Expense View rhtml
<%= text_field_with_auto_complete :category, :name %>
Auto complete works for the first category. How do I get it working for multiple categories? e.g. Category1, Category2
Intended behavior: like the StackOverflow Tags textbox
Update:
With some help and some more tinkering, I got multiple comma-seperated autocomplete to show up (will post code-sample here).
However on selection, the last value replaces the content of the text_field_with_auto_complete. So instead of Category1, Category2.. the textbox shows Category2 when the second Category is selected via auto-complete. Any ideas how to rectify this?

If you are just trying to support multiple instances of autocomplete per field, you can pass a delimiter to the autocomplete options with the symbol :token. This provides a delimiter to allow multiple results. Stackoverflow would use :token => ' ' (there should be a space between the quotes, but the autoformat is removing it) to specify space at the delimiter between multiple takes although ',' is more commonly used.

This is not quite your question, but I wouldn't recommend using HABTM anymore. You should create a join model and use has_many :through. (In your case you'd create a new model called ExpenseCategoryAssignment, or something)
The problem is that HABTM creates ambiguities that rails doesn't like, and it tends to expose bugs you wouldn't see otherwise.

You need to use "data-delimiter" param like this
<%= f.autocomplete_field :brand_name, welcome_autocomplete_brand_name_path, "data-delimiter" => ', ' %>

Related

Rails collection_select: using 'prompt' with logic - is there a better way?

So, I am using a form_tag to create my own search facility on a site displaying items.
I have a collection_select which lists locations of the items. I initially used prompt: 'n/a' to display 'n/a' without adding 'n/a' into the list of locations and then tried to use select: params[:search][:location] to keep the selection in the list after submitting. Basically I could get one of them to work at a time. Never both. I have written a work-around below:
<%= collection_select(:search, :location, Item::LOCATIONS, :to_s, :titleize, prompt: (#location ? params[:search][:location] : 'n/a')) %>
This is working but it feels wrong. I have obviously created a variable called #location in the controller for when the params exist and then added logic into the collection_select tag.
Any cleaner solutions to this would be appreciated. I am very new to Rails (and coding!) so trying to learn best practices.

Adding a country tag separate from acts as taggable list

My goal is to find a way to add a new field in the submission form to gather what country the picture is from. I want to make the country searchable the same way any tag can lead to a page just with items containing that tag when using the acts-as-taggable-on gem. I made a few very, very misguided attempts with my early October attempts at https://github.com/harrisongill/whatyoutheat/commits/master.
Ideally, I want it to look like when you go to a page like http://community.whatyoutheat.com/pins/10, there are not only lines like description, author, and tags, but also one for country that works similar to how a tag works.
Any help would be appreciated. I'm open to something involving acts-as-taggable-on or anything else.
Easy peasy. And looking at your code, there's not much to do.
First of all, you need a virtual attribute for country since it's a field that doesn't exist in your database and as far as I can tell, doesn't need to. And because it's rails 3, you'll need to make it accessible. Next you'll merge the country name with the tags list before you submit.
That's it.
Step 1 - Add a virtual attribute in the model
attr_accessor :country
Step 2 - Make the attribute accessible in the model
attr_accessible :description, :image, :image_remote_url, :tag_list, :country
Step 3 - Append the country name to the tag list before submit in the controller#create
params[:pin][:tag_list] = params[:pin][:tag_list] + ", " + params[:pin][:country]
params.delete :country
#pin = current_user.pins.new(params[:pin])
Also remember to replace the tag list in Show to create links to the tags.
<p>Tags: <%= raw #pin.tag_list.map { |t| link_to t, tag_path(t) }.join(', ') %></p>

How to submit multiple NEW items via Rails 3.2 mass-assignment

I have a pretty standard use-case. I have a parent object and a list of child objects. I want to have a tabular form where I can edit all the children at once, as rows in the table. I also want to be able to insert one or more new rows, and on submit have them be created as new records.
When I use a fields_for to render a series of sub-forms for nested records related by has-many, rails generates field names e.g. parent[children_attributes][0][fieldname], parent[children_attributes][1][fieldname] and so on.
This causes Rack to parse a params hash that looks like:
{ "parent" => {
"children" => {
"0" => { ... },
"1" => { ... } } }
When passed a new (un-persisted) object, the same fields_for will generate a field name that looks like:
parent[children_attributes][][fieldname]
Note the [] with no index in it.
This cannot be posted in the same form with the fields containing [0], [1], etc. because Rack gets confused and raises
TypeError: expected Array (got Rack::Utils::KeySpaceConstrainedParams)
"OK", thinks I. "I'll just make sure all the fields use the [] form instead of the [index] form. But I can't figure out how to convince fields_for to do this consistently. Even if I give it an explicit field name prefix and object:
fields_for 'parent[children_attributes][]', child do |f| ...
So long as child is persisted, it will automatically modify the fieldnames so that they become e.g. parent[children_attributes][0][fieldname], while leaving fieldnames for new records as parent[children_attributes][][fieldname]. Once again, Rack barfs.
I'm at a loss. How the heck do I use standard Rails helpers like fields_for to submit multiple new records, along with existing records, have them be parsed as an array in the params, and have all the records lacking IDs be created as new records in the DB? Am I out of luck and I just have to generate all the field names manually?
As others have mentioned, the [] should contain a key for new records because otherwise it is mixing a hash with an array type. You can set this with the child_index option on fields_for.
f.fields_for :items, Item.new, child_index: "NEW_ITEM" # ...
I usually do this using the object_id instead to ensure it is unique in case there are multiple new items.
item = Item.new
f.fields_for :items, item, child_index: item.object_id # ...
Here's an abstract helper method that does this. This assumes there is a partial with the name of item_fields which it will render.
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
You can use it like this. The arguments are: the name of the link, the parent's form builder, and the name of the association on the parent model.
<%= link_to_add_fields "Add Item", f, :items %>
And here is some CoffeeScript to listen to the click event of that link, insert the fields, and update the object id with the current time to give it a unique key.
jQuery ->
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
That code is taken from this RailsCasts Pro episode which requires a paid subscription. However, there is a full working example freely available on GitHub.
Update: I want to point out that inserting a child_index placeholder is not always necessary. If you do not want to use JavaScript to insert new records dynamically, you can build them up ahead of time:
def new
#project = Project.new
3.times { #project.items.build }
end
<%= f.fields_for :items do |builder| %>
Rails will automatically insert an index for the new records so it should just work.
So, I was not happy with the solution I saw most often, which was to generate a pseudo-index for new elements, either on the server or in client-side JS. This feels like a kludge, especially in light of the fact that Rails/Rack is perfectly capable of parsing lists of items so long as they all use empty brackets ([]) as the index. Here's an approximation of the code I wound up with:
# note that this is NOT f.fields_for.
fields_for 'parent[children_attributes][]', child, index: nil do |f|
f.label :name
f.text_field :name
# ...
end
Ending the field name prefix with [], coupled with the index: nil option, disables the index generation Rails so helpfully tries to provide for persisted objects. This snippet works for both new and saved objects. The resulting form parameters, since they consistently use [], are parsed into an array in the params:
params[:parent][:children_attributes] # => [{"name" => "..."}, {...}]
The Parent#children_attributes= method generated by accepts_nested_attributes_for :children deals with this array just fine, updating changed records, adding new ones (ones lacking an "id" key), and removing the ones with the "_destroy" key set.
I'm still bothered that Rails makes this so difficult, and that I had to revert to a hardcoded field name prefix string instead of using e.g. f.fields_for :children, index: nil. For the record, even doing the following:
f.fields_for :children, index: nil, child_index: nil do |f| ...
...fails to disable field index generation.
I'm considering writing a Rails patch to make this easier, but I don't know if enough people care or if it would even be accepted.
EDIT: User #Macario has clued me in to why Rails prefers explicit indices in field names: once you get into three layers of nested models, there needs to be a way to discriminate which second-level model a third-level attribute belongs to.
The common solution is to add a placeholder into [], and replace it with a unique number on inserting the snippet to the form. Timestamp works most of the time.
Maybe you should just cheat. Put the new records in a different faux attribute that is a decorator for the actual one.
parent[children_attributes][0][fieldname]
parent[new_children_attributes][][fieldname]
It's not pretty, but it should work. It might take some extra effort to support round-trips to the form for validation errors.
I've came across this user case in all my last proyects, and I expect this to continue, as julian7 pointed, it is necesary to provide a unique id inside the []. In my opinion this is better done via js. I've been dragging and improving a jquery plugin for dealing with this situations. It works with existing records and for adding new records but expects a certain markup and it degrades gracefully, heres the code and an example:
https://gist.github.com/3096634
Caveats for using the plugin:
The fields_for call should be wrapped in a <fieldset> with data-association attribute equal to the pluralized name of the model, and a class 'nested_models'.
an object should be built in the view just before calling fields_for.
the object fields perse should be wrapped in a <fieldset> with class "new" but only if the record is new (cant remember if I removed this requirement).
A checkbox for the '_destroy' attribute inside a label must exist, the plugin will use the label text to create a destroy link.
A link with class 'add_record' should exist within the fieldset.nested_models but outside the fieldset enclosing the model fields.
Appart from this nuisances its been working wonders for me.
After checking the gist this requirements must be clearer.
Please let me know if you improve on the code or if you use it :).
BTW, I was inspired by Ryan Bates first nested models screencast.
long post deleted
Ryan has an episode on this:
http://railscasts.com/episodes/196-nested-model-form-revised
It looks like you need to generate the unique index manually. Ryan uses the object_id for this.
I think you can make it work by including the id of the record as a hidden field
There is a gem called cocoon for doing this, I would go for a leaner mor DIY aproach but it was specifically built for this cases.

What's the best way to edit many objects of a single class in one Rails form?

I'm working on a Rails form that will allow the user to edit the attributes of many objects of a class with a single submission. My initial instinct was to create an outer form_for block and then iterate through the items within it using fields_for.
However, there is no object that bears a one-many relation to the objects the form will modify, and so it seems to me that there is no object that would be correct to pass into form_for.
In any case, what I'd like to see is an example of a form that modifies multiple objects simultaneously without appealing to a "parent" object. Perhaps this will involve form_tag?
(Note: I'm working in haml, so answers in haml would be awesome though unnecessary.)
Well, having a parent object will make it easier.
For bulk updates of many objects, the key is to use the right input name so that rails will parse the params as a array, i.e.
#posts.each do |post|
fields_for "posts[#{post.id}]", post do |p|
p.text_field :name
p.hidden_field :id
end
end
Have a look at the generated html source to see what name attribute the text input gets. If this is done right, params[:posts] will now be a hash in the controller which you can then update.
http://railscasts.com/episodes/165-edit-multiple should be relevant too
There are some extra wrinkles to my actual situation, but here's some pseudocode that I hope will illustrate the approach I've taken:
= form_tag url_for(:controller => "the_controller",
:action => "update") do
#objects_to_be_updated.each do |object|
= check_box_tag "[desired_path][through_the][parameters_hash]", true, object.boolean_attibute
= text_field_tag "[another_path][through_the][parameters_hash]", object.text_attribute
end
end
And so on.
Using the _tag variants of the form helpers, which don't require association with an Active Record model, is a bit of a pain but also seems to give you more control over structure of the resulting parameters.

select field, text field and foreign key

I would like to know which way is the best to resolve my question :
I have a form in order to select people via a select field. If the name is missing in the select field, a text field is available to add directly the person's name.
- The form in new.html.erb is the format of the new action of the Team controller.
- The list of the people is extracted from the People model.
def new
#team = Team.new
#people = People.all
end
I created an attribute in the Team model to store the new_person text field :
class Team < ActiveRecord::Base
attr_accessor :new_person
...
end
Finally, here's an extract of my view :
<%= f.select :person_id, #people.map { |p| [p.name, p.id] } %>
<%= f.text_field :new_person %>
Obviously, I would like to save the new person in the table Person before saving the data from the form. As usual, the id are saved instead of the names
At this point, I've got two issues :
1/ The params array has the key new_person what doesn't have the table. So it is not possible to use the Team.new(params[:team]) method. Does exist an easy solution to avoid this problem ?
2/ As I need the person_id, how can I get it when the name comes from the new_person field? In using the before_filter method ?
Thanks a lot,
Camille.
1) You should consider using fields_for in your view within your form_for block. This will allow you to specify that the fields within the fields_for block are attributes of a different model, will generate the appropriately named input fields, and allow you to use params[:team] in your controller. See the FormHelper documentation for more on this.
2) While you could do something in your controller to first check for a value in the new_person field, create the record, update the contents of params[:team] with the value of the newly created person and create the team, this feels a bit like a hack to me. Another possible solution which may be less fragile would be to use some JavaScript on the page that would render some kind of modal dialog for the user to create the new person, submit the new person to the person#create controller method, then refresh your drop down. It would probably not be terribly difficult to do this using a jQuery UI modal form (very good example at that link to do what you need) with Rails remote form and unobtrusive JavaScript.
This is probably a more difficult solution to your second question than you are hoping for, but probably more useful in the long run.

Resources