Rails Nested Object Form *_attributes - ruby-on-rails

I'm using Rails 2.3.2, and trying to get a nested object form to work properly. I've narrowed my problem to the issue that Rails is not setting my nested form elements with the *_attributes required to initiate the accepts_nested_attributes_for processing.
My model code is:
class Person < Party
has_one :name, :class_name => "PersonName"
accepts_nested_attributes_for :name, :allow_destroy => true
end
class PersonName < ActiveRecord::Base
belongs_to :person
end
My view code looks like this (I'm using HAML):
%h3 New customer
= error_messages_for :person, :person_name, :name, :country
- form_for :person, :url => collection_url, :html => {:class => 'MainForm'} do |person_form|
- #person.build_name unless #person.name
- person_form.fields_for :name do |name_form|
= name_form.label :given_name, "First Name:"
= name_form.text_field :given_name
= name_form.label :family_name, "Last Name:"
= name_form.text_field :family_name
= hidden_field_tag :inviter_id, params[:inviter_id]
= hidden_field_tag :inviter_code, params[:inviter_code]
%p= submit_tag "Create"
= link_to 'Back', collection_url
Instead of params being:
{"person"=>{"name_attributes"=>{"given_name"=>"Fred", "family_name"=>"Flintstone"}}, ...}
I get:
{"person"=>{"name"=>{"given_name"=>"Fred", "family_name"=>"Flintstone"}}, ...}
As a result, I get a TypeMismatch exception. I've followed the documentation from Ryan Daigle. I've also followed the advice from this blog and the complex-forms-example.
Using Firebug, I went through my form and adjusted the name attribute of the input tags from name to name_attributes. This produced the params with name_attributes, and the create worked fine.
I'm stuck as I cannot figure out why my form is not producing the *_attributes form of the name.
Another thing I tried is I got the complex_form_example working in my environment. I've gone through every inch of the controller, models and views and compared it to my code. I cannot find what is different. I know this is something small, and would appreciate any help!
Thanks!

Post backs do not get routed to the right place
def new
#person = Person.new
end
<% form_for #person do |f| %>
<% f.fields_for :name_attributes do |p| %>
...
<% end %>
<% end %>
Post backs get routed to the right place
def new
#person = Person.new
#person.name = PersonName.new # << this allows fields_for :relation vs :relation_attributes
end
<% form_for #person do |f| %>
<% f.fields_for :name do |p| %>
...
<% end %>
<% end %>
No need to #person.name again in #create

Try to use an actual object for form_for:
form_for :person => form_for #person

I have just been struggling for about an hour with exactly the same problem!
Follow nowk's pattern for the new method in the controller, then put this in your view
<% form.fields_for :name, #person.name do |name_form| %>
<% end %>
Good luck if you try it, that's what worked for me.

Not sure why this isn't working for, but as a workaround, you could just use params[:name] in your controller#create method to update the person record.
person = Person.new(params[:person])
person.name << PersonName.new(params[:name])

Unfortunately, I still have not been able to figure out why this form wouldn't work with nested object forms. I stripped it down to the simplest data, started over using the complex-form-example as a start. I ended using the active_presenter to gather x-object data from the form. I'll revisit nested object forms sometime in the form. Thanks for your help.

Thought I would share my solution as it's slightly different - in my case I want the nested attributes to be dynamic.
In new action:
case params[:type]
when "clubber"
#account = resource.send "build_#{params[:type]}"
when "promoter"
#account = resource.send "build_#{params[:type]}"
when "company"
#account = resource.send "build_#{params[:type]}"
when "venue_owner"
flash[:notice] = 'We ask that venue owners register via the web. Thanks.'
redirect_to root_path and return
end
In my view:
= f.fields_for #account.class.name.downcase+'_attributes' do |form|
Pretty ghetto, but it works.

Related

Rails - inserting multiple records/dynamic form

I'm working on a dynamic form in a Rails app, and I need to insert a variable number of records into a model in a single form submission. I've done this using PHP -> MySQL/Postgres before, but I have no idea how to do it in Rails.
Ultimately, users should be able to create any number of records to be inserted, but in my example below, I'm limiting it to 2... let me see if I can do that, first...
Here's the form - the ids all get a unique suffix because they are being populated dynamically from localStorage objects on submission.
new.html.erb
<%= form_for #entry, html: {id: :new_entry_form} do |f| %>
<% for i in 0..1 %>
<%= f.text_field :name, :id => 'name_#{i}' %>
<%= f.text_field :day, :id => 'day_#{i}' %>
<% end %>
<% end %>
Here's the associated controller - I'm sure that this is missing something, but I don't know what.
def new
#entry = Entry.new
end
def create
#entry = Entry.create(entry_params)
redirect_to "http://localhost:3000/entries"
end
private
def entry_params
params.require(:entry).permit(:name, :day)
end
Any help would be much appreciated.
Follow this link it shows how to create multiple object in one form submit:
http://vicfriedman.github.io/blog/2015/07/18/create-multiple-objects-from-single-form-in-rails/

nested associations in form not updating properly

I have the following situation. A contact has_many lead_profiles. I have a new lead_profile form, where the user enters information about the lead_profile and contact. However, I am using a jquery autofill plugin, in that if the contact already exists, the user can select it and then a hidden input id attribute is created to indicate that it's no longer a new contact:
# controller
def new
#lead_profile = LeadProfile.new
#contact = #lead_profile.build_contact name: "Donato"
end
# view
<%= form_for #lead_profile do |f| %>
<%= f.fields_for :contact do |builder| %>
<%= builder.text_field :name %>
<%= builder.text_field :address %>
<% end %>
<% end %>
# javascript
$('#lead_profile_contact_attributes_address').bind('typeahead:selected', function(obj, datum, name) {
var $parent = $("#lead_profile_contact_attributes_address").closest(".form-group");
$parent.after('<input name="lead_profile[contact_attributes][id]" type="hidden" value="' + contact.id + '">')
});
Now when the form is submit to create, I need to determine if the contact exists or not. Note that if I didn't do this, and sent an id attribute for contact back to server, Rails raises an exception "Couldn't find Contact with ID=10 for LeadProfile with ID=". So I must override the contact_attributes= call:
def contact_attributes=(params)
if params[:id].present?
self.contact = Contact.find(params[:id])
else
self.contact = Contact.new params
end
end
Above, I determine if the contact is a new contact or an existing one (which would be the case if the user populates it from javascript autocomplete).
All seems to work fine. If the contact is new, a new contact is created in database with attributes from params hash. If contact is existing, the existing contact is updated with attributes from form. And in both cases, the lead_profiles record is associated with that contact.
But now a problem occurs when I want to update the lead_profile. I use the same form for update as I did for new:
<%= form_for #lead_profile do |f| %>
<%= f.fields_for :contact do |builder| %>
<%= builder.text_field :name %>
<%= builder.text_field :address %>
<% end %>
<% end %>
If I update the contact info in the lead_profile form, the contact is not updated in the database, because of this override that I needed:
def contact_attributes=(params)
if params[:id].present?
self.contact = Contact.find(params[:id])
else
self.contact = Contact.new params
end
end
If I remove that, then the contact would be updated, however then I would get the other issue when creating a new contact:
Couldn't find Contact with ID=10 for LeadProfile with ID=
I really want to save this two models together in a single form, and Rails shouldn't restrict me on that. What can I do at this point?
Ok, so I have one solution working. Remember that when you do an update on a model, it automatically does an update on associations as soon as you invoke save on the model. By default, it will update its association attributes based on the input in the fields_for nested form. However, as soon as you override that default and invoke find, then obviously nothing will be updated on the association:
def contact_attributes=(params)
if params[:id].present?
self.contact = Contact.find(params[:id])
else
self.contact = Contact.new params
end
end
The solution is to somehow distinguish when you are updating a model and when you are dynamically building an existing model association through the form via autocomplete. I used a virtual attribute for that purpose. If the field is being dynamicalyl generated by autocomplete, this is not a rails default and you have to handle it yourself:
model Contact < ActiveRecord::Base
attr_accessor :autocompleted
end
model LeadProfile < ActiveRecord::Base
def contact_attributes=(params)
# this contact model was autocompleted via javascript
if params[:id].present? && params[:autocompleted].present?
self.contact = Contact.find(params[:id])
else
# self.contact = Contact.new params
super
end
end
In all other cases, call super and let Rails do its default behavior.
Just a guess try adding the contact instance in your fields_for call i.e.
<%= f.fields_for :contact,#contact do |builder| %>
<%= builder.text_field :name %>
<%= builder.text_field :address %>
<% end %>
Let me know if that works. I've often had random issues with nested forms and sometimes explicitly setting the target object of the nested form solves the problem

Rails - nested forms to link has_one's where the child object has already been created

I have two models which are linked in a has_one / belongs_to association; Computer and Ipv6Address respectively.
I have pre-populated the Ipv6 table with all the entries that I want it to have, and I now need to have a drop-down list on the Computer new/edit forms to select an item from Ipv6 to associate it with.
Everything I've seen so far on this only seems to work when you are creating both objects at the same time on the new form and then subsequently editing them.
I've tried to set up my MVC's as per the examples I've found online, but I keep getting errors, as underneath these code excerpts:
Computer model:
class Computer < ActiveRecord::Base
accepts_nested_attributes_for :ipv6_address
has_one :ipv6_address
...
Ipv6Address model:
class Ipv6Address < ActiveRecord::Base
attr_accessible :computer_id, :ip_address
belongs_to :computer
...
Computer controller:
class ComputersController < ApplicationController
def new
#computer = Computer.new
#ipv6s = Ipv6Address.where('computer_id IS NULL').limit(5)
end
def edit
#computer = Computer.find(params[:id])
#ipv6s = Ipv6Address.where('computer_id = #{#computer.id} OR computer_id IS NULL').order('computer_id DESC').limit(5)
end
Computer new form:
<%= simple_form_for( #computer ) do |f| %>
<%= f.fields_for :ipv6_addresses do |v6| %>
<%= v6.input :ipv6_address, :collection => #ipv6s %>
<% end %>
<% f.button :submit %>
<% end %>
Error when browsing to computer new form:
NoMethodError in ComputersController#new
private method `key?' called for nil:NilClass
No line of code reference is given for this error, but it only appears when I have the nested form included in the computer new form.
Any ideas as to what is causing it or how I can better go about what I'm doing?
EDIT:
As it turns out, I needed to have accepts_nested_attributes_for :ipv6_address after the line has_one :ipv6_address in the Computer model.
That fixed the issue with the form loading.
As per Yarden's answer, I then also singularized all instances of "ipv6_address" so as to reflect the has_one relationship.
Once doing that in the new form, however, the ipv6 field completely disappeared. I'll open a new question with this one if I can't get it sorted out shortly.
Try adding ':', I think that may be the problem :
<%= f.fields_for :ipv6_addresses do |v6| %>
Also you forgot to add a '.' here:
#ipv6s = Ipv6Address.where('computer_id IS NULL').limit(5)
Edit after change:
The problem is that its only a has_one relationship so it doesnt know the plural of :ipv6_address as you stated in your model... you need to change it to :ipv6_address instead of :ipv6_addresses...
Also in your form change the :ipv6_address to the actual field which is :ip_address.
So overall your form should look like this:
<%= simple_form_for( #computer ) do |f| %>
<%= f.fields_for :ipv6_address do |v6| %>
<%= v6.input :ip_address, :collection => #ipv6s %>
<% end %>
<% f.button :submit %>
<% end %>

Anyone know how to save many objects in one form?

I am trying to save many new objects in a form to one pre-existing parent object.
- form_for :parent_object do |f|
This is the beginning of my form. And then within it, I would do:
- 2.times do
- fields_for :child_object do |f|
Now if I were to save this, it would render as an ParentObject_Controller Update action which would fail because Update doesn't identify new objects.
So if I wanted to render the appropriate Save action, I would have to set up like this :
- form_for [#parent_object, #child_object] do |f|
- 2.times do
- fields_for :child_object do |f|
This form then renders the Save action, but only saves the last child_object.
I would show you my controller, but there's hardly a point because its devastatingly erroneous.
My question is, how would you save many new objects in a form to one pre-existing parent object?
I have looked extensively at Ryan Bate's work, and many many other blogs and SO posts regarding this. Nothing seems to really point at specifically creating new child objects for one pre-existing parent object.
Update:
I am under the impression that I have to toggle the parent_object's controller actions for def update.
elsif params[:parent_object][:child_object]
#child_object = Child_Object.new(params[:child_object])
if #child_object.valid? && #parent_object.referrals << #child_object
redirect_to new_parent_object_child_object_path(#parent_object)
else
render :action => :new
end
In debugger, if I I place a debugger at the root of def update, and I write :
>> params[:parent_object]
#=> nil
Interesting! That means that when child_object is send to parent_object controller, the params are not filled out for it. Haha, no idea what to do about it though..
Unfortunately that code doesn't work, it was just my attempt at getting closer. ;)
OK, let's give it another shot. Code taken from RB's screencast with replaced object names:
<% form_for #parent_object do |f| %>
<%= f.error_messages %>
<!-- some field of parent object here -->
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<% f.fields_for :child_objects do |builder| %>
<!-- some fields for child objects -->
<p>
<%= builder.label :content, "Some content for child object" %><br />
<%= builder.text_area :content, :rows => 3 %>
<%= builder.check_box :_destroy %>
<%= builder.label :_destroy, "Remove child object" %>
</p>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
This is a form for #parent_object that has fields for :child_objects. Of course, you've to replace fields with your own.
To make this work, you'll have to build child objects in the constructor:
def new
#parent_object = ParentObject.new
3.times { #parent_object.child_objects.build }
end
Similarly in the edit method, you'd do:
def edit
#parent_object = ParentObject.find(params[:id])
3.times { #parent_object.child_objects.build }
end
To make it work, you need to define the nested attributes for child object:
class ParentObject < ActiveRecord::Base
has_many :child_objects, :dependent => :destroy
accepts_nested_attributes_for :child_objects
end
Hope this helps - this is exactly what RB proposes in his screencasts. Let me know in the comments if you need some further explanation.
-- EDIT --
The update method in the parent_object_controller.rb is just a standard one:
def update
#parent_object = ParentObject.find(params[:id])
if #parent_object.update_attributes(params[:parent_object])
flash[:notice] = "Successfully updated parent object."
redirect_to #parent_object
else
render :action => 'edit'
end
end
But thanks to the accepts_nested_attributes_for in the ParentObject, the nested instances will be created as well.
I didn't include all the model and controller code in this response. You can see the rest of the code by downloading source code for this episode from github.
You can take a look at this answer I gave to a similar question. There're two options: with separate forms, or with a single form.
You'll just have to change the moderate_names_path to the correct path to your parent model instance (and of course the set of fields you want to modify). You can do it with polymorphic_path:
polymorphic_path([#parent_object, #child_object])

virtual model and form_for (or formtastic)

Sometimes we need form without model creation - for example search field or email, where should be send some instructions. What is the best way to create this forms? Can i create virtual model or something like this? I'd like to use formtastic, but not form_tag.
Firstly, Formtastic doesn't need a model in all cases, although it certainly works best and requires less code with a model.
Just like Rails' own built-in form_for, you can pass in a symbol instead of an object as the first argument, and Formtastic will build the form and post the params based on the symbol. Eg:
<% semantic_form_for(:session) do |f| %>
...
<% end %>
This will make the form values available to your controller as params[:session].
Secondly, a model doesn't mean an ActiveRecord model. What I mean is, Formtastic will work with any instance of a class that quacks like an ActiveRecord model.
A classic example of this that many people are using Authlogic for authentication with Formtastic. Part of Authlogic is the idea of a UserSession model, which works fine:
Controller:
def index
#user_session = UserSession.new
end
Form:
<% semantic_form_for(#user_session) do |f| %>
<%= f.input :login %>
<%= f.input :password %>
<% end %>
This will make your form data available in your controller as params[:user_session].
It's really not that hard to create a model instance to wrap up the concerns of your model. Just keep implementing the methods Formtastic is expecting until you get it working!
default_language.rb
class DefaultLanguage
attr_accessor :language_id
end
foo_controller.rb
def index
#default_language = params[:default_language] || Language.find_by_name("English")
end
index.erb
<% semantic_form_for #default_language do |form| %>
<% form.inputs :id => 'default_language' do %>
<%= form.input :id,
:as => :select,
:collection => #languages,
:required => false,
:label => "Primary Language:",
:include_blank => false %>
<% end %>
<% end %>
I used AJAX to post the form when the value changed.
Or you simply create a form with form_for and leave the model reference blank.
for example
<% form_for "", :url=>some_url do |f| %>
<%= f.text_field "some_attribute" %>
<%= submit_tag "submit" %>
You can fetch the values by simply saying params[:some_attribute] in your controller.

Resources