Adding fields to nested forms - ruby-on-rails-3

So I have a model called City and it has_many :places and it accepts_nested_attributes_for :places. Each Place belongs_to :category. When I render a form for a City I have f.fields_for :places do |place| and I do it like this:
<% f.fields_for :places do |place| %>
<%= render "place_fields", :f => place
<% end %>
My _place_fields.html.erb contains the folowing:
<div class="place_header"><%= f.object.category.name %></div>
<div><%= f.label :name %>: <%=f.text_field :name %> </div>
<div><%= f.text_area :description %> </div>
But the problem apears when I try to add a new place. First of all I want to bring up a simple select form to select a category for the new place, and then render that same partial based on the category_id.
I do that inside the same action:
def add_place
if params[:category_id]
#place = Place.new(:category_id => params[:category_id])
respond_to do |format|
format.html { return nil }
format.js {
#here comes the render
}
end
else
render_message :header => "Choose category", :partial => "category_select", :over => 10
end
end
But if I try to do $("#places_tab").append("<%= escape_javascript(render :partial => "place_fields", :f => #place %>"); it gives an error, wich is expected.
Once again: I need to render the fields for that new Place and just don't know how to do that.
UPDATE
Received some advice on passing the original City Formbuilder to the action and rendering that Place right from that builder, but don't have any idea of how to do that.

The problem is that you are passing an instance of the Place model (#place) as the form builder instead of the form builder itself.

Related

Rails 3 - checkbox for create (opposite of _destroy)

I have a Query model with a has_many relationship to OutputFields. In my query controller's new function I build several OutputFields within the query instance. In my form, I want each checkbox to determine whether the object is saved (a check means save this instance of OutputField to the database). How can I do this?
my models:
class Query < ActiveRecord::Base
attr_accessible :description, :name
has_many :output_fields, :dependent => :destroy
accepts_nested_attributes_for :output_fields
end
class OutputField < ActiveRecord::Base
attr_accessible :query_id, :column_name, :table_name
belongs_to :query
end
relevant sections of my queries controller. Structure is another model.
# GET /queries/new
# GET /queries/new.json
def new
#query = Query.new
Structure.columns.each do |column|
#query.output_fields.build( :table_name => Structure.table_name, :column_name => column.name )
end
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #query }
end
end
Finally, my view. Right now I'm linking the checkbox to the destroy attribute, which I think will do the exact opposite of what I want.
<%= form_for(#query) do |f| %>
<%= f.fields_for :output_fields do |builder| %>
<div class="field">
<%= builder.check_box :_destroy %>
<%= builder.label :_destroy, builder.object.column_name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
If it's not obvious, I'm trying generate a user interface for a simple query builder. This is my first rails app, so any advice is appreciated.
By default the value of the check_box form helper is to set the checked_value to '1' and the unchecked_value to '0'. So to reverse the behaviour of the destroy checkbox, just switch these values around.
<%= builder.check_box :_destroy, {}, '0', '1' %>

Rails 3 Cocoon link_to_add_association Renders Partial Twice

My partial gets rendered twice instead of only once, as expected. Any thoughts?
Here's my Person view
<%= simple_nested_form_for(#person) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :name %>
<h3>Records by year</h3>
<div id='records'>
<%= f.simple_fields_for :records do |record| %>
<%= render 'record_fields', :f => record %>
<% end %>
<div class='links'>
<%= link_to_add_association 'New Record', f, :records, :class => 'btn' %>
</div>
</div>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Models (removed things such as constants and validations):
class Person < ActiveRecord::Base
attr_accessible :name, :records_attributes
has_many :records, :dependent => :destroy
accepts_nested_attributes_for :records, :reject_if => :all_blank, :allow_destroy => true
end
class Record < ActiveRecord::Base
attr_accessible :price, :status, :year, :person_id
belongs_to :person
end
My _record_fields.html.erb partial looks like this:
<div class='nested-fields well'>
<%= f.input :price %>
<%= f.input :year %>
<%= f.input :status, :collection => record_statuses, :include_blank => false %>
<%= link_to_remove_association "Remove", f %>
</div>
An interesting issue is that, if I change where the partials are generated (so 'after' instead of the default 'before' the link_to_add_association link), it generates a partial after the button, but the duplicate is generated before the link_to_add_association link.
The only similar issues reported on here I could find were with caching in production. This is happening in development, and my caching is turned off (by default).
Am I missing something? Can anyone help?
Edit:
Looking at the cocoon JavaScript, it seems the click event is called twice for one click (tested it with $('.add_fields').click(), which triggers $('.add_fields').live('click', function() {...} ) twice. I'm still at a loss as to why this might be happening. Input thoroughly appreciated.
Edit #2:
Controller:
# GET /persons/new
# GET /persons/new.json
def new
#person = Person.new
# #person.records.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #person }
end
end
# GET /persons/1/edit
def edit
#person = Person.find(params[:id])
end
I was having this same issue.
My problem was I requiring the cocoon javascript twice.
Once in my application.js
// app/assets/javascripts/application.js
//= require cocoon
and once in my application layout
/ app/views/layouts/application.html.haml
= javascript_include_tag :cocoon
after removing the include tag from my layout it began working as expected
Had the same issue, found that in my application layout (app/views/layouts/application.html.erb) I had already included application, and in application.js I had included cocoon. When including application.js in my view with cocoon, cocoon would fire twice.
(Same as above, but slightly different as I didn't explicitly specify cocoon in my application.html.erb, I specified application.js which included cocoon)
This happen when from application.js, you require two times cocoon, commonly, when you try change of jQuery to vanilla Javascript

Submitting AJAX form for a nested child with another nested child (AssociationTypeMismatch)

My models
I'm trying to create a form for an Annotation. This annotation belongs to a Map, and each annotation should have one Boundary. A map can have many annotations.
I first created the association by letting both Annotation and Map has_one Boundary, but later I switched to using a polymorphic boundary_object. The error was the same regardless.
has_one :boundary, :as => :boundary_object # <= Map
has_one :boundary, :as => :boundary_object # <= Annotation
belongs_to :boundary_object, :polymorphic => true # <= Boundary
Views and Controller
Here's the thing: First I used Boundary.new to create a new boundary object here, since I didn't have a pre-set annotation object, since the form can be submitted multiple times.
maps/show.html.erb
<%= form_for([#map, Annotation.new], :remote => true ) do |f| %>
<%= f.text_area :body, :cols => 80, :rows => 10, :style => "width: 500px" %>
<%= f.fields_for Boundary.new do |b| %>
<%= b.text_field :ne_x, :style => "display:none" %>
<%= b.text_field :ne_y, :style => "display:none" %>
<%= b.text_field :sw_x, :style => "display:none" %>
<%= b.text_field :sw_y, :style => "display:none" %>
<% end %>
<% end %>
I could use f.fields_for :boundary too, if I have this in the maps_controller.rb:
#annotation = #map.annotations.build
#annotation.boundary = Boundary.new
But the result is still the same.
annotations_controller.rb
def create
#annotation = Annotation.new(params[:annotation])
respond_to do |format|
if #annotation.save
format.js { }
end
end
The Error
When submitting that form, this results in the following error at the first line in the create method.
ActiveRecord::AssociationTypeMismatch (Boundary(#2158793660) expected, got ActiveSupport::HashWithIndifferentAccess(#2165684420))
Obviously, the form works without the whole boundary thing. These are the parameters submitted:
{
"utf8"=>"✓",
"authenticity_token"=>"6GDF6aDc6GMR3CMP+QzWKZW9IV9gSxfdkxipfg39q7U=",
"annotation"=>
{
"body"=>"foo bar",
"boundary"=>
{
"ne_x"=>"11312",
"ne_y"=>"5919",
"sw_x"=>"6176",
"sw_y"=>"1871"
}
},
"map_id"=>"1"
}
What do I have to do to be able to create the Boundary object for this annotation right away?
According to your associations:
First, you need to build a new boundary object (see here for more info):
def show
#map = ...
#annotation = #map.annotations.build
#boundary = #annotation.build_boundary # build new boundary
end
Second, you need to edit your view:
<%= form_for([#map, #annotation], :remote => true ) do |f| %>
<%= f.text_area :body, :cols => 80, :rows => 10, :style => "width: 500px" %>
<%= f.fields_for :boundary do |b| %>
...
<% end %>
<% end %>
Third, check that you have accepts_nested_attributes_for for your Boundary in the Annotation model.
accepts_nested_attributes_for :boundary
The form will then look like this – note that the name of the association needs _attributes:
<input … name="annotation[boundary_attributes][ne_x]" … />

Rails3 Associations and nested attributes

I am having trouble when it comes to saving/creating 2 objects at once and associating them to one another. Currently I am doing it in a 'hackish' sort of way by not using nested forms and just passing the parameters for both objects separately (from the view.) Then I connect them in the controller here is my code:
Models
class Post < ActiveRecord::Base
belongs_to :user
has_one :product
accepts_nested_attributes_for :product, :allow_destroy => true
end
class Product < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
View
<%= form_for(#post) do |f| %>
<div id="post_field">
<%= f.text_area :content %>
</div>
<div id="post_link_previewer" class="clearfix">
<%= fields_for :product do |prod| %>
<%= prod.text_field :name %><br />
<%= prod.text_area :description, :rows => 2 %><br />
<%= prod.text_field :image_url %><br />
<%= prod.text_field :original_url %>
<% end %>
</div>
<div id="submit" class="clearfix">
<%= f.submit "Post" %>
</div>
<% end %>
PostsController
def create
#user = current_user
#post = #user.posts.create(params[:post])
#product = Product.create(params[:product])
#post.product_id = #product.id
respond_to do |format|
if #post.save
format.html { redirect_to(root_path, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
So when a user makes a post, they can attach a 'product' to that post if they want. The current way I am doing it makes a lot of sense. When I looked at nested form tutorials and see them using build methods I start to get a little confused as to what is going on. Can you help me understand the best way of linking these 2 objects upon create? Is it best to use nested form fields? I feel the current way I am doing it isn't as efficient as it should be.
Yes, you should use nested forms. There is a reason as to why they were built. They ease the process of managing associations and creating nested objects in a single go.
The build method builds an object (it calls the .new() method for the object) and then you can use it.
I advise you to start with a simple example of nested forms and play around with it for an hour or two. This way, you'll have a better understanding of what's happening underneath.
I think, in this case, self-learning by playing would help you a lot, instead of someone just telling you why nested forms are better.
To get you started, refer to nested-attributes-in-rails.

undefined method `to_key' for #<Class:0x17a6408> -rails-3

I am experiencing problems with undefined method `to_key' for, on a form for polymorphic upload.
This is the form partial:
<%= form_for [#parent, Upload], :html => { :multipart => true } do |f| %>
<div class="field">
<%= f.label :document %><br />
<%= f.file_field :document %>
</div>
<div class="actions">
<%= f.submit "Upload"%>
</div>
<% end %>
This is the controller:
class UploadsController < ApplicationController
before_filter :find_parent
respond_to :html, :js
def index
#uploads = #parent.uploads.all unless #uploads.blank?
respond_with([#parent, #uploads])
end
def new
#upload = #parent.uploads.new unless #uploads.blank?
end
def show
#upload = #parent.upload.find(params[:upload_id])
end
def create
# Associate the correct MIME type for the file since Flash will change it
if params[:Filedata]
#upload.document = params[:Filedata]
#upload.content_type = MIME::Types.type_for(#upload.original_filename).to_s
#upload = #parent.uploads.build(params[:upload])
if #upload.save
flash[:notice] = "suceessfully saved upload"
redirect_to [#parent, :uploads]
else
render :action => 'new'
end
end
end
def edit
#upload = Upload.where(params[:id])
end
private
def find_parent
classes ||= []
params.each do |name ,value|
if name =~ /(.*?)_id/
#parent = classes << $1.pluralize.classify.constantize.find(value)
end
end
return unless classes.blank?
end
end
If i change
<%= form_for [#parent, Upload], :html => { :multipart => true } do |f| %>
to
<%= form_for [parent, Upload], :html => { :multipart => true } do |f| %>
I get a new error: undefined local variable or method `parent' for #<#:0x21a30e0>
This is the error trace:
ActionView::Template::Error (undefined method `to_key' for #<Class:0x2205e88>):
1: <%= render :partial => "uploads/uploadify" %>
2:
3: <%= form_for [#parent, Upload], :html => { :multipart => true } do |f| %>
4:
5:
6: <div class="field">
The "uploads/uploadify" partial is in this gist: https://gist.github.com/911635
Any pointers will be helpful. Thanks
From what I can see, your form_for should be something along the lines of
<%= form_for [#parent, #upload], :html => { :multipart => true } do |f| %>
as I'm assuming your upload object is nested within another object, similar to the following:
resources :posts do
resources :uploads
end
What form_for does when passed an array like this is construct the relevant path based on the class of the given objects and whether they are new records.
In your case, you create a new upload object in the new action of your controller, so form_for will inspect the array, get the class and id of #parent, then get the class and id of #upload. However, because #upload has no id, it will POST to /parent_class/parent_id/upload instead of PUTting to parent_class/parent_id/upload/upload_id.
Let me know if that doesn't work and we'll figure it out further :)
-- EDIT - after comments --
This means that one of #parent or #upload is nil. To check, you can put the following in your view
<%= debug #parent %>
and the same for #upload and see which is nil. However, I'm guessing that #upload is nil, because of this line in your controller:
# UploadsController#new
#upload = #parent.uploads.new unless #uploads.blank?
specifically the unless #uploads.blank? part. Unless you initialize it in the ApplicationController, #uploads is always nil, which means #uploads.blank? will always be true, which in turn means #upload will never be initialized. Change the line to read
#upload = #parent.uploads.new
and the problem will hopefully be resolved. The same is true of the other methods where you have used unless #uploads.blank?.
On a semi-related note, in UploadsController#find_parent, you have this line
classes ||= []
because the variable is local to the find_parent method, you can be assured that it is not initialized, and should rather write classes = [].
Also, you have this line of code
return unless classes.blank?
right before the end of the method. Did you add that in so that you return from the method once #parent has been initialized? If so, that line should be inside the each block.
Further, since classes isn't used outside of the method, why define it at all? The code could read as follows and still have the same behaviour
def find_parent
params.each do |name ,value|
#parent = $1.pluralize.classify.constantize.find(value) if name =~ /(.*?)_id/
return if #parent
end
end
Amongst other things, you'll see that this does a few things:
Avoids initializing a variable that is not needed.
Inlines the if statement, which helps readability for single line conditionals
Changes use of unless variable.blank to if variable. Unless your variable is a boolean, this accomplishes the same thing, but reduces the cognitive load, as the former is essentially a double negative which your brain has to parse.
-- EDIT - from email exchange about the issue --
You are correct - if #parent will return true if parent is initialized. As I mentioned on SO however, the exception to this is if #parent is initialized and set to false. Essentially what it means is that in Ruby, all values except nil and false are considered true. When an instance variable has not been initialized, it's default value is nil, which is why that line of code works. Does that make sense?
In terms of setting #parent in each action that renders form in the UsersController, which of these is the correct way to do this on the index action. I have tried all 3 but got errors
Remember that both #parent and #upload must be instances of ActiveRecord (AR) objects. In the first case, you set #parent to User.all, which is an array of AR objects, which will not work. Also, you try to call #parent.uploads before #parent is initialized, which will give a no method error. However, even if you were to swap the two lines around, you are calling #parent.uploads when parent is an array. Remember that the uploads method is defined on individual AR objects, and not on an array of them. Since all three of your implementations of index do similar things, the above caveats apply to all of them in various forms.
users_controller.rb
def index
#upload = #parent.uploads
#parent = #user = User.all
end
or
def index
# #user = #parent.user.all
#parent = #user = User.all
end
or
def index
#parent = #upload = #parent.uploads
#users = User.all
end
I'll quickly walk you through the changes I made. Before I start, I should explain that this
<%= render "partial_name", :variable1 => a_variable, :variable2 => another_variable %>
is equivalent to doing this
<%= render :partial => "partial_name", :locals => {:variable1 => a_variable, :variable2 => another_variable} %>
and is just a shorter (and somewhat cleaner) way of rendering. Likewise, in a controller, you can do
render "new"
instead of
render :action => "new"
You can read more about this at http://guides.rubyonrails.org/layouts_and_rendering.html Now on to the code.
#app/views/users/_form.html.erb
<%= render :partial => "uploads/uploadify" %>
<%= form_for [parent, upload], :html => { :multipart => true } do |f| %>
<div class="field">
<%= f.label :document %><br />
<%= f.file_field :document %>
</div>
<div class="actions">
<%= f.submit "Upload"%>
</div>
<%end%>
On the uploads form, you'll see that I changed #parent and #upload to parent and upload. This means you need to pass the variables in when you render the form instead of the form looking for instance variable set by the controller. You'll see that this allows us to do the following:
#app/views/users/index.html.erb
<h1>Users</h1>
<table>
<% #users.each do |user| %>
<tr>
<td><%= link_to user.email %></td>
<td><%= render 'uploads/form', :parent => user, :upload => user.uploads.new %></td>
</tr>
<% end %>
</table>
Add an upload form for each user in UsersController#index. You'll notice that because we now explicitly pass in parent and upload, we can have multiple upload forms on the same page. This is a much cleaner and more extensible approach to embedding partials, as it becomes immediately obvious what parent and upload are being set to. With the instance variable approach, people unfamiliar with the code base might struggle to determine where #parent and #upload are being set, etc.
#app/views/users/show.html.erb
<div>
<% #user.email %>
<h3 id="photos_count"><%= pluralize(#user.uploads.size, "Photo")%></h3>
<div id="uploads">
<%= image_tag #user.upload.document.url(:small)%>
<em>on <%= #user.upload.created_at.strftime('%b %d, %Y at %H:%M') %></em>
</div>
<h3>Upload a Photo</h3>
<%= render "upload/form", :parent => #user, :upload => user.uploads.new %>
</div>
This is similar to the changes above, where we pass in the parent and upload objects.
#config/routes.rb
Uploader::Application.routes.draw do
resources :users do
resources :uploads
end
devise_for :users
resources :posts do
resources :uploads
end
root :to => 'users#index'
end
You'll see that I removed uploads as a top level resources in the routes. This is because uploads requires a parent of some sort, and so cannot be top level.
#app/views/uploads/new.html.erb
<%= render 'form', :parent => #parent, :upload => #upload %>
I made the same changes as above, passing parent and upload through explicitly. You'll obviously need to do this wherever you render the form.
#app/controllers/users_controller.rb
class UsersController < ApplicationController
respond_to :html, :js
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
redirect_to users_path
else
render :action => 'new'
end
end
def update
#user = User.find_by_id(params[:id])
#user.update_attributes(params[:user])
respond_with(#user)
end
def destroy
#user = User.find_by_id(params[:id])
#user.destroy
respond_with(#user)
end
end
I've removed any mention of #parent from the user controller, as we pass it through explicitly.
Hopefully that all makes sense. You can extrapolate from these examples and pass through the parent and upload object wherever you want to render an upload form.
[#parent, Upload] => [#parent, :upload]
<%= form_for [#parent, :upload], :html => { :multipart => true } do |f| %>
UPD
You should change places :upload and #parent
<%= form_for [:upload, #parent], :html => { :multipart => true } do |f| %>