Anyone know how to save many objects in one form? - ruby-on-rails

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])

Related

Ruby on Rails multiple images connected to one object

I've been trying to create a form that would get parameters for multiple models. I have a photo model that belongs to a product model and I want to make it so that when you create a new product you can also upload images that are linked to that product by id.
<%= form_for #product, html:{multipart:true} do |f| %>
<div class="field">
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<%=form_for #photo do |t| %>
<%t.productID = f.id%>
<div class="field">
<%= t.label (:image) %>
<%= t.file_field (:image) %>
</div>
<%end%>
<div class="actions">
<%= f.submit %>
</div>
<%end%>
right now I'm using paperclip for image attachments and the photo model accepts the images as parameters. I've used paperclip before but the product could only have one image connected to it. If I use the form above I get "First argument in form cannot contain nil or be empty" error and it points to where the form_for #photo starts.I have controllers for both with the usual methods of new, create, update, etc. I've routed resources to both product and photos but I'm still pretty new to rails and don't fully understand how this stuff works.
I think what you're trying to do is a good application for nested forms using the fields_for helper.
First, you'll need to ensure that your product model and photo model have the right associations (A product probably has_many photos, and a photo belongs to a product, right?). Then you'll make sure the product class 'accepts nested attributes for photo's which allows you to add attributes to the photos model from a products form.
in products.rb
class Product
has_many :photos
accepts_nested_attributes_for :photos
end
and in photo.rb
class Photo
belongs_to :product
end
Then you'll want to make sure any attributes you need for the photo are white-listed in your product params.
in products_controller.rb
private
def product_params
params.require(product).permit(:first_product_attribute, :second_produtc_attribute, photo_attributes: [:image])
end
Last, you'll create the form using the special helper fields_for
in your view
<%= form_for #product, html:{multipart:true} do |f| %>
<div class="field">
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<%= f.fields_for :photo do |t| %>
<div>
<%= t.label :image %>
<%= t.file_field :image, :multiple => true %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<%end%>
You'll also need to make sure you're actually creating new photo objects in your product's create action:
in products_controller.rb
def create
#product = Product.new(product_params)
if #product.save!
params[:photo]['image'].each do |img|
#photo = #product.photos.create!(:image => img)
end
flash[:success] = 'product saved!'
redirect_to #product
end
end
Some of this is based on my experience doing the same thing but with Carrierwave instead of Paperclip so your specific implementation might be a little different.
I dont think this is a proper method <%t.productID = f.id%>. Maybe try <% t.text_field :productID, value = f.id, type = hidden %> or something along those lines?
heres some docs for the form helper so you know what to put after t.abcd
http://apidock.com/rails/v3.2.3/ActionView/Helpers/FormHelper/form_for
You're getting the
"First argument in form cannot contain nil or be empty"
..error because #photo is nil, you need to set it in your controller #photo = Photo.new.
Also, form tags inside form tags are invalid HTML.
https://www.w3.org/TR/html5/forms.html#the-form-element
Forms
Content model: Flow content, but with no form element
descendants.
You want to use f.fields_for instead. Learn how to use it here here
I have controllers for both with the usual methods of new, create,
update, etc.
You only ever hit one controller and action when you go to a path, say /photos will only hit the photos controller (as configured in your routes.rb). This I think is where you're messing up the #photo variable. Set both in the same controller in order for the view to be able to see both variables.

Creating a Model in Rails within the controller of a different Model

I'm trying to implement a quote saving feature in a Rails app. I have a User Model that has_many :quotes and a Quote Model that belongs_to :user. Now I have a separate Book Model that would be the source of these quotes.
Within the Book's show.html.erb file, I have a form to save quotes for the current user
<%= form_for (#new_quote) do |f| %>
<div class="field">
<%= f.hidden_field :book_id, :value => #new_comment.book_id %>
<%= f.text_field :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And in the Book controller, I have
def show
#new_quote = current_user.quotes.create(book_id: params[:id])
end
The quote saves fine but the problem is, since I have this Quote creation statement in the show method, everytime I go to the show.html.erb page of my Book model, it creates a Quote with an empty body.
What can I do to solve this? I was thinking it probably would involve moving this Quote creation to the actual create method of the Quote controller but I don't know how to exactly pass the parameters through.
You could just build that quote, but not save it to the database. Then the user need to send the form to save that record. Just change your show method to:
def show
#new_quote = current_user.quotes.build(book_id: params[:id])
end

When I create a database item it saves multiple times and returns 'no data received'

I am making one of my first rails apps from scratch and cannot figure out what is going on. Whenever I try to create a new instance, it saves multiple instances of the item. I am trying to make tasks, and each time I create a new instance it saves 7 times, and then gives me an error page saying no data received. I tried adding a uniqueness validator for the model, but that would save the first instance then return me to root path. This is what I have:
Task Controller:
class TasksController < ApplicationController
def index
#tasks = Task.all
end
def create
#task = Task.new(task_params)
if #task.save
redirect_to 'tasks'
else
redirect_to root_path
end
end
private
def task_params
params.require(:task).permit(:title, :category, :difficulty)
end
end
Here is the form I am using, perhaps that is the problem?
<div id='task_form'>
<%= form_for :task do |f| %>
<%= f.label :title %>
<%= f.text_field :title%>
<%= f.label :category %>
<%= f.text_field :category%>
<%= f.label :difficulty %>
<%= f.text_field :difficulty%>
<%= f.submit %>
<% end %>
</div>
Thanks if anyone knows what this is or has experienced it before
Do this:
#app/controllers/tasks_controller.rb
def new
#task = Task.new
end
#app/views/tasks/new.html.erb
<%= form_for #task do |f| %>
--
Problem
I frankly don't know why you're getting multiple submits, but I can see one of the issues you have is you're using a symbol in place of an instance variable.
This wouldn't normally be an issue, but as you're experiencing these problems, I would certainly use an instance variable in the form. (if only to test).
This does several important things:
Using an #instance var creates an ActiveRecord object for your form
The use of an ActiveRecord object gives rails a definite structure for the object
By saving the object, Rails is able to populate the appropriate attributes etc
I would certainly try the above code, but there may be other issues at work here if your form is submitting 7 times.

The better way to pass the foreign_key value to the Rails controller

It's been almost a week since I've began to dig deeper in forms , associations , hashes , symbols... But it seems I cannot solve the puzzle without your help .
I am working on a project for displaying different galleries content . The basic idea is when the user sees the names of galleries (names are links ) to be able to click on chosen one. Then all the images ,that belong to this gallery , are displayed . On the bottom there should be a link "Add image in this gallery" .
My models :
class Gallery < ActiveRecord::Base
attr_accessible :name
has_many :pictures
end
class Picture < ActiveRecord::Base
attr_accessible :image
belongs_to :gallery
end
I have created index on gallery_id for the 'pictures' table .
My big problem appears here , how to pass the gallery_id to the controller's action 'new' . As I've seen in "Agile web development with Rails" it could be :
<%= link_to 'Add a picture here...',new_picture_path(:gallery_id=>#gallery.id) %>
As it seems in this case the foreign_key :gallery_id is exposed in the URL bar of the browser . The second problem is that :gallery_id is available for the controller 'new' function , but "disappears" for the 'create' function (causing an error " Couldn't find Gallery without an ID ") .
The problem is gone when I add a hidden field in the _form for pictures , in my case :
<%= form_for(#picture) do |f| %>
<div class="field">
<%= f.hidden_field :gallery_id , :value=>params[:gallery_id] %>
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
Here are my definitions in the 'pictures' controller :
def new
#gallery=Gallery.find(params[:gallery_id])
#picture=#gallery.pictures.build
end
def create
#gallery = Gallery.find(params[:gallery_id])
#picture = #gallery.pictures.new(params[:picture])
if #picture.save
redirect_to(#picture, :notice => 'Picture was successfully created.')
else
redirect_to(galleries ,:notice => 'Picture was NOT created.')
end
end
And finaly the link_to definition in show.html.erb for galleries:
<% for picture in selpics(#gallery) %>
<div id= "thumb" >
<%= image_tag picture.image %>
</div>
<% end %>
<%= link_to 'Add a picture here...',new_picture_path(:gallery_id=>#gallery.id) %>
Here is the debug output before submitting the image :
--- !map:ActiveSupport::HashWithIndifferentAccess
gallery_id: "6"
action: new
controller: pictures
and after submitting the 'create' button (with exception raised ) :
{"utf8"=>"✓",
"authenticity_token"=>"IGI4MfDgbavBShO7R2PXIiK8fGjkgHDPbI117tcfxmc=",
"picture"=>{"image"=>"wilsonblx.png"},
"commit"=>"Create"}
As you see , there is nothing like "gallery_id" in the "pictures" hash .
Summarizing my questions to you :
Is there a way to pass the foreign_key without hidden_field ?
Could I hide somehow passing the foreign key form showing in the URL bar ?
Is there an alternative on passing arguments using 'link_to' ?
Thank you .
You may want to consider reading the Rails Guide on nested resources:
http://guides.rubyonrails.org/routing.html#nested-resources
In a nutshell:
routes.rb
resources :galleries do
resources :pictures do
end
# Generates the routes: /galleries/:gallery_id/pictures
pictures_controller.rb
def new
#gallery = Gallery.find(params[:gallery_id])
#picture = Picture.new
end
def create
#gallery = Gallery.find(params[:gallery_id]) # gallery_id is passed in the URL
#picture = #gallery.build(params[:picture])
if #picture.save
# success
else
# fail
end
end
pictures/new.html.erb
<%= form_for [#gallery, #picture] do |f| %>
<div class="field">
<%= f.hidden_field :gallery_id , :value=>params[:gallery_id] %>
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
Ok, so the gallery_id is still passed through the URL, but I don't really see anything wrong with that. You have to pass it somewhere, right? You really only have 3 sane choices on where to pass it: a hidden field, as a querystring parameter, or tucked away inside the URL (nested resource). Of the 3, the latter is IMHO the cleanest method.
If you want to make things even easier on yourself, I highly recommend looking into Jose Valim's Inherited Resources gem that takes care of a lot of this boilerplate nastiness for you:
https://github.com/josevalim/inherited_resources
You need not use the numeric ID's in your RESTful routes. Look at permalink_fu, and use the :permalink field rather than the :id to refer to each gallery resource.
/galleries/louvre
/galleries/moma/382
And
... new_picture_path(:gallery_id => #gallery.permalink)
The key here is using a symbolic, unique key that's not the ID, permalink's are pretty good for that.
You can choose to pass the permalink in as :id and update your controller actions to expect that.

Rails Nested Object Form *_attributes

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.

Resources