Ruby on Rails - Forms with belongs_to association - ruby-on-rails

On Rails 4. I have three Models (for this question): Users, Organizations, and UserOrganizations. Users can have many organizations, and organizations can have many users. This relationship is stored in UserOrganizations with user_id and organization_id. So, UserOrganizations belongs to users and organizations.
When I want to add a new user/organization relationship, the user_id is automatically taken from the current user logged in. However, to assign that user to an organization, I have a dropdown select, listing all the organizations in the database (by name).
This is fine in the dev environment but not so great when I will eventually have over a thousand organizations.
What I would like to do is have a sort of text look-up input where the user can type in an organization's name and then all orgs in the database containing that name will display. Then the user can select the name (through a radio button maybe?) to tell the app which org he/she would like to be assigned. Ideally, this would happen on the same page/no reload.
What is the best way to create this form? Is there a gem or something else that exists to easily make this? Can you do this with formtastic or even without a gem? Thank you for any help.

Make a text_field_tag (For the search).
<%= text_field_tag(:for_search, "", :onchange=> "search_orgs(this)") %>
On the onchange event of that text_field, put a JS function that runs an AJAX request that sends as a parameter the string you want to search on.
<script>
function search_orgs(theString)
{
var dataString = 'string='+theString;
$.ajax(
{
type: "GET",
url: "/your_path/show(or_create)",
data: dataString
});
return false;
}
</script>
That way, everytime your textfield changes, the string you enter will be send as a parameter to the controller (in this example, the show method).
In your show method, capture the param, use it to filter your data, and then populate the select_tag.
Your Controller
def show
string = params[:string]
yourFiltered_data = Yourmodel.where("name ILIKE = '#{string}'").all
yourFiltered_data .each do |d|
#htmlSelect+="<option value = #{d.organization_id}>#{d.organization_name}</option>"
end
end
Now, in a show.js.erb file in your view folder, you put this
show.js.erb file
$(document.getElementById("theOrgs").innerHTML = '<%= select_tag :organization_id, #htmlSelect.html_safe %>')
That way, everytime your textbox value changes, the select_tag options will also change and will only feature those companies that contain the string your submitted in their names.
Hope this helps!

You should probably take a look to rails3-jquery-autocomplete gem. It has quite documentation and examples. More or less:
Model:
class UserOrganization < ActiveRecord::Base
attr_accessor :organization_name
end
Controller:
class UserOrganizationsController < Admin::BaseController
autocomplete :organization, :name
end
Routes:
resources :user_organizations do
get :autocomplete_organization_name, :on => :collection
end
View:
form_for(#user_organization) do |f|
f.hidden_field :organization_id, id: 'org_id'
f.autocomplete_field :organization_name, autocomplete_organization_name_user_organizations_path, id_element: '#org_id'
end
It also provides integration with SimpleForm and Formtastic.

Related

More than a simple nested form

campaign.rb
class Campaign < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :item
end
item.rb
class Item < ActiveRecord::Base
belongs_to :campaign
end
Campaign has 2 attributes: title and description
Item has 1 attirubte: name
I'll try explain myself by words, I want to create a nested form where they user insert the campaign's name and description but he can insert more than just 1 item, he can insert a list of items (in particular there will be a "+" button that when clicked a new item row will appear and the user can insert items).
At the end all is send all together clicking just one submit button.
How can I reach my goal with rails?
I answered a question just yesterday. Here's the link: Rails accepts_nested_attributes_for with f.fields_for and AJAX
I'll write out how it works & provide some resources to help give you some more ideas:
How It Works
Loading associative fields into a form is done by using f.fields_for
You'll do it like this:
#app/views/campaigns/new.html.erb
<%= form_for #campaign do |f| %>
<%= f.fields_for :items do |a| %>
<%= a.text_field :information %>
<% end %>
<% end %>
In order to get this to work, you have to build the associated ActiveRecord objects in the backend before you render the view, like this:
#app/controllers/campaigns_controller.rb
def new
#campaign = Campaign.new
#campaign.items.build
end
Adding Extra Fields Via Ajax
Adding extra fields with Ajax requires engineering a new solution to the issue
The way you do this is to take the f.fields_for text & put it into a partial. This partial can be called from the original view, as well as another view (which we can render through Ajax)
The Ajax part works by basically taking a request from your form (the ajax request), and then using another action in your controller to build a new ActiveRecord object & render another partial that will contain another form. This partial will then call the original f.fields_for partial, allowing you to render another field
Your Ajax can then extract the new field & append it to your page. The way you get around the id issue (keeping the IDs sequential & unique) is to employ the child_index method, and use Time.now.to_i to generate a timestamp
If you read my answer referenced at the top of this answer, all of this will make sense :)
Some great resources for this:
RailsCasts Nested Forms
Adding Fields With Ajax
A nice gem along with tutorial is available from ryanbates who is the author of railscasts.com site.You can use this and have a look at tutorial here
And also if you want to try manually use the fields_for while writing in the form like here and manage some jquery code for add or remove.

Rails scaffolding belongs_to - showing #<MyClass:xxxx>

Experienced Java developer, new to Rails - wondering about belongs_to relationship in scaffolding.
Saw another answer like this
Does rails scaffold command support generate belongs_to or many to many model middle table migration info?
and followed the rails generate scaffold_controller obj:references pattern.
The index/show page is showing #<MyClass:xxxx> instead of the string I want - is there a method in the target class (parent side of the belongs_to) I need to override to specify the identifier?
Also in the edit view, it looks like it's trying to modify the reference as a string rather than as drop-down - is there something I need to specify to make that happen?
Thanks!
BTW - I was able to get similar scaffolding to work in Django and Grails, where the foreign key turned into a drop-down; I'm hoping Rails is equally easy and I'm just missing it.
You can override the #to_s method on the instances, as it is the one being called.
class FooDoodle < ActiveRecord::Base
def to_s
name
end
end
That's when showing a record.
However, when you're actually using the form to set the associations, scaffold will only generate an input field in the view so you can enter the id. You could have a dropdown menu for example, but the options for that dropdown would somehow have to be selected in a manner.
For example, if there are 2000 possible associated records, which ones do you show? Do you show the 2000? Only the first 10? That logic would go into your controller.
So, for example:
class FooDoodlesController < ApplicationController
def edit
#foodoodle = FooDoodle.find(params[:id])
#friends = #foodoodle.possible_friends # or else
end
end
and using select and options_for_select as choices
# _form.html.erb
<%= form_for #foodoodle do |f| %>
<%= f.label :friend %>
<%= f.select :friend, #friends.map{ |p| [p.to_s, p.id] } %>

How to calculate a default value for a form text field in Ruby on Rails?

I have a form for payments like this:
<%= f.label :invoice_id %>
<%= f.select(:invoice_id, current_user.outstanding_invoices_collection) %>
<%= f.label :amount %>
<%= f.text_field :amount %>
I wonder if there's a way to populate the value of the amount text field somehow, e.g. with the open balance of the associated invoice?
In my Invoice model have this function:
def balance
payments.map(&:amount).sum - total
end
How can this be done?
Im assuming you want to populate the text box based on the selection of a invoice from dropdown. In that case
The idea is
You need to make a ajax call onchange of the invoice dropdown.
That ajax response should update the value of the text box.
And with rails-3 i think its recommended to do this in unrobustive way. Here is a link you can follow. Start playing with it meanwhile I will try to make something functional. Hope to get a good result again.
Are you looking for how to populate the value only?
Update:
Here is the ajax part
#Application.js or any sutable js file
$(function($) {
$("#your_drop_down_id").change(function() {
#Your the url to your controller action here
$.ajax({url: '/get_amount',
data: 'invoice_id=' + this.value,
dataType: 'script'})
});
});
#in get_amount Action
invoice = Invoice.find(params[:invoice_id]) #Other appropriate logic to get the invoice
#amount = invoice.balance
#get_amount.js.erb
$('#your_text_box_id').val('<%= #amount %>');
#routes.rb
#This part is written following the gist: https://gist.github.com/3889180 by #TinTin
resources :payments do
collection do
get 'get_amount'
end
end
Let me know if any part is confusing to you.
In your controller you can assign any value to any field, and it will be displayed in the view.
def new
#payment = new Payment()
#payment.amount = 100
end
If you want some dynamic value, e.g: based on a combobox selection, then do it in javascript or with AJAX.

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.

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