create new object in rails3 using multimodel form - ruby-on-rails

I am n00b as rails is concerned. I am trying yo create a single multimodel form in my first rails3 project. Details are given below:
class Item < ActiveRecord::Base
# attr_accessible :title, :body
has_many :item_reviews, :dependent => :destroy
accepts_nested_attributes_for :item_reviews
end
and
class ItemReview < ActiveRecord::Base
# attr_accessible :title, :body
belongs_to :item
end
So as clear, an item can have multiple reviews but when I am creating an item, I want at least 1 review for it. So I want to get item and first review in single form while item creation.
I am using following view:
<%provide(:title,'Create')%>
<h1> Add an Item review</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for (#item) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<% f.fields_for :item_reviews, #item.item_reviews do |ff| %>
<%= ff.label :shop_address %>
<%= ff.text_field :shop_address %>
<% end %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
<% f.fields_for :item_reviews, #item.item_reviews do |ff| %> will not work because there is not item_review associated with #item currently (#item = Item.new) Until I save #item, I can't create new item_review. What should I do in that case.
I know one possibility is model independent form but can't I use something above to make life easy.
PS: I am using bootstrap, just in case if that helps.

There is some way to achieve an instance with item reviews. The key is to create an instance with some of nested instances without actual saving
#item = Item.new
#item.item_reviews.build
and then in your form
form_for #item do |f|
...
f.fields_for :item_reviews do |ff|
with this code an instance of review is present and you can render form

Related

Rails 4, edit multiple model in one form

I'm working on the following feature :
In one page, the user can upload multiple pictures at one time, and then on the following page he can edit the fields for those pictures.
So what i want is basically a form to edit multiple model at one time (the model are not yet saved in the database).
The application use the https://github.com/bootstrap-ruby/rails-bootstrap-forms to generate forms, but if you have a solution with only the form helpers from rails it would maybe be enough to help me to solve my problem.
I'm currently trying
<%= bootstrap_form_tag(url: import_save_client_pictures_path(#client), layout: :horizontal) do |f| %>
<% #imported_pictures.each_with_index do |picture, index| %>
<%= bootstrap_form_for([#client, picture], url: import_save_client_pictures_path(#client), layout: :horizontal) do |ff| %>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-4 col-lg-4">
<%= image_tag(picture.file_tmp_url, class: 'img-responsive') %>
</div>
<div class="col-xs-12 col-sm-12 col-md-8 col-lg-8">
<%= ff.text_field :title, value: picture.title, required: true %>
<%= ff.text_field :description_en, value: picture.description_en %>
<%= ff.text_field :description_fr, value: picture.description_fr %>
<%= ff.text_field :description_de, value: picture.description_de %>
<%= ff.text_field :copyright, value: picture.copyright %>
<%= ff.collection_select :country, country_sorted_list_with_first_country(:XX), :first, :second, selected: picture.country %>
<%= ff.collection_check_boxes :category_ids, #client.categories.visible_for(current_user), :id, :title %>
<%= ff.text_field(:reference) if current_user.global_admin? %>
</div>
</div>
<%= '<hr />'.html_safe if (index + 1) != #imported_pictures.count %>
<% end %>
<% end %>
<div class="form-actions">
<%= f.primary %>
<% end %>
<%= link_to t(:cancel), [#client, :pictures], class: 'btn btn-default' %>
</div>
But when i click on sending the form, nothing happen.
I'm thinking on going to only one form and generate all the fields for each picture with setting the fields's names to something like 'field_name_index', but it's not very elegant.
What i try to achieve is to have some sort of array that is passed to the controller with the data being like pictures = [picture_1_fields, picture_2_fields] and so
Do you guys can help me?
Thanks :)
Edit: To be precise, Pictures are not nested form or some other models
This answer requires you to make an effort to understand the concepts and actually impliment it yourself. Make sure you read the documentation thoughly!
Setup accepts_nested_attributes_for in the parent model:
class Client < ApplicationRecord
has_many :pictures
accepts_nested_attributes_for :pictures
validates_associated :pictures
end
class Picture < ApplicationRecord
belongs_to :client
end
Then use fields_for to generate fields for the nested records:
<%= bootstrap_form_tag(url: import_save_client_pictures_path(#client), layout: :horizontal) do |f| %>
<%= f.fields_for :pictures do |ff| %>
<%= ff.text_field :title, value: picture.title, required: true %>
<%= ff.text_field :description_en, value: picture.description_en %>
# ...
<% end %>
<% end %>
You might notice that there are no inputs if a Client has no pictures. To solve this you need to seed the association with new records. This is usually done in in the action that presents the form:
def new
3.times { #client.pictures.new }
end
To permit the nested params you want to use a nested array of keys:
def import_save_client_pictures
if #client.update(picture_params)
redirect_to '/somewhere'
else
render :some_view
end
end
def picture_params
params.require(:client)
.permit(pictures_attributes: [:title, :description_en])
end
But consider using AJAX to save/update the child records in individual POST/PATCH requests behind the scenes. It usually gives a better user experience and only requires a standard CRUD controller.

Forms for nested_attributes (has_many: mobiles)

Contact Model
class Contact < ActiveRecord::Base
belongs_to :phonebook
has_many :mobiles
accepts_nested_attributes_for :mobiles
end
Mobile Model
class Mobile < ActiveRecord::Base
belongs_to :contact
end
Contacts Controller
def new
#contact = Contact.new
end
Note:
Mobile model has details and type as attributes.
Contact model has name as attribute.
How do I write my form with rails helper to produce multiple mobile objects for a new contact instance?
This is my form
<h1>Create a new Contact</h1>
<h2>Add details</h2>
<% form_for(#contact) do |f| %>
<%= f.error_messages %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :mobile %><br />
<% f.fields_for :mobile do |ff| %>
<div>
<%= ff.select :type,options_for_select([["HOME", "H"], ["WORK", "W"],["OTHER", "O"]])%>
<%= ff.text_field :details %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit "Add Contact" %>
</div>
<% end %>
Take a look here. You will need something like this in your controller:
def new
#contact = Contact.new
2.times { #contact.mobiles.build}
end
An explanation of what build does is to found in the API docs:
build(attributes = {}, &block) Link
Returns a new object of the collection type that has been instantiated
with attributes and linked to this object, but have not yet been
saved. You can pass an array of attributes hashes, this will return an
array with the new objects.
Update
If you want to add fields dynamically take a look at:
Adding dynamic fields to nested form through AJAX
https://github.com/lailsonbm/awesome_nested_fields

Nested Form Unknown Attribute Error

I'm trying to build a small expense tracking app. Using the nested_form gem to add line items. There is an Expense model which accepts nested attributes. Items belong to expenses.
class Expense < ActiveRecord::Base
belongs_to :organization
belongs_to :department
has_many :expense_types
has_many :items
accepts_nested_attributes_for :items
end
The items model:
class Item < ActiveRecord::Base
belongs_to :expense
end
The controller create action action looks like:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
end
def create
#expense = Expense.new(expense_params)
if #expense.save
flash[:notice] = "Expense Report Submitted"
redirect_to #expense
else
render 'new'
end
end
private
def expense_params
params.require(:expense).permit(:department_id, :expense_type_id, :notes, items_attributes: [:id, :description, :amount, :issue_date, :_destroy])
end
end
The new expense form looks like:
<%= nested_form_for (#expense) do |f| %>
<% if #expense.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#expense.errors.count, "error") %> prohibited
this expense from being saved:</h2>
<ul>
<% #expense.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class"row">
<div class="col-md-8">
<div class="form-group">
<%= f.label :department_id %><br>
<%= f.collection_select(:department_id, Department.all, :id, :department_name, prompt: true, class: "dropdown-menu") %>
</div>
<div class="form-group">
<%= f.label :expense_type_id %><br>
<%= f.collection_select(:expense_type_id, ExpenseType.all, :id, :expense_name, prompt: true, class: "form-control") %>
</div>
<%= f.fields_for :items do |i| %>
<div class="form-group">
<%= i.label :description%>
<%= i.text_field :description, class: "form-control" %>
</div>
<div class="form-group">
<%= i.label :amount%>
<%= i.text_field :amount, class: "form-control" %>
</div>
<div class="form-group">
<%= i.label :issue_date%>
<%= i.date_select :issue_date, class: "form-control" %>
</div>
<%= i.link_to_remove "Remove", class: "btn btn-default" %>
<% end %>
<div><p><%= f.link_to_add "Add Expense", :items, class: "btn btn-default" %></p></div>
<div class="form-group">
<%= f.label :notes %>
<%= f.text_area :notes, class: "form-control" %>
</div>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
I was able to save expenses before adding the nested attributes. After doing that, whenever I hit the submit button, I get the ActiveRecord::UnknownAttributeError in ExpensesController#create error. It's weird to see unknown attribute: expense_id. Did I miss something here?
Nested Form
To the best of my knowledge, the nested_form gem basically just creates a fields_for instance for your form. If this is the case (it more or less has to be), then you've got several issues you need to consider
By the way, looking at the nested_form documentation, it seems it was primarily designed for Rails 3. When it mentioned about attr_accessible, it now means to focus on strong_params (if you're using Rails 4 of course)
--
Build
As mentioned by Mandeep, you need to ensure you're building the correct ActiveRecord objects for your form. form_for works by taking an ActiveRecord object & populating the various attributes etc with its own attributes.
This is how Rails creates the illusion of persistence with this functionality -- it takes the ActiveRecord & populates the same data recursively
In order to get fields_for to work (which is the basis of nested_attributes, you need to build the associative ActiveRecord object in the action which renders the form_for (in your case new):
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def new
#expense = Expense.new
#expense.items.build #-> required for fields_for to work
end
end
Remember, the nested_form gem is not magic - it is essentially just a javascript plugin to replicate the fields_for elements rendered in your form already, and then append them to the DOM.
It essentially uses the child_index: Time.now.to_i "trick" to surmount the incremental id isue
--
Attributes
Secondly, you need to appreciate the error you're receiving
expense_id could be an attribute in the items_attributes objects. I've not seen this with pure Rails, but perhaps the nested_form gem appends a particular attribute to the objects or something
Either way, I believe the problem will be how to associate your items objects with your parent Expense object. To do this, I would do the following:
Check your params hash (see where expense_id is being passed)
Update your strong_params to allow the expense_id
I KNOW THE PROBLEM
DATABASE - you likely don't have the expense_id column in the items table
Fix
You need to create a migration to put the expense_id foreign_key into your items table
To do this, you should open your CMD and perform the following:
$ rails generate migration AddExpenseIDToItems
Then you can change the migration to have the following line:
add_column :expense_id, :items
Then you just need to do:
$ rake db:migrate
This should resolve your issue
Your controllers new action should be like this
def new
#expense = Expense.new
#item = #expense.items.build
end
Also if you are using rails 4 then you don't need nested_form_for gem. Checkout nested forms. In your form you can simply use
<%= form_for #expense do |f| %>
// expense fields
<%= f.fields_for #item do |e| %>
// item fields
<% end %>
<%= f.submit %>
<% end %>

No form will be shown?

Here I have the following models
Customer
Book
Book_Manager
My association are has follow
BookManager
belongs_to :customer
Customer
has_many :book_managers, :dependent => :destroy
Book isn't required at this stage
This is based on the nested model form from Railscasts but I can't seem to see why it doesn't work. The application should work has follow
A customer goes into customer#edit then see BookManagers (the last one created) if any. If not then simply see blank and a form to allow him to create a new book_manager which will in the future have a book associated. But i am not at that stage.
Here the customer controller
def edit
#customer = Customer.find(params[:id])
#bookmanager = BookManager.first_or_create
end
The render file his has follow
<h3>Book</h3>
<div><%= render 'book_managers/form' %></div>
Here the _form.html.erb file in book_managers
<% form_for (#bookmanager) do |f| %>
<div class="field">
<%= f.label :customer_id %><br />
<%= f.text_field :customer_id %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I know it get render properly, if i put scrap words in it, i will see them outside the form. But i am not to sure if bookmanager is actually empty,nil or properly set up
Change <% form_for (#bookmanager) do |f| %> to
Rails 3:
<%= form_for (#bookmanager) do |f| %>
Rails 2:
<%- form_for (#bookmanager) do |f| %>
You need a - (Rails 2) or = (Rails 3) after the <%.

Multiple children in single form in rails

I have a model that has an arbitrary number of children entities. For simplicity lets call the entities Orders and Items. I would like to have a create Orders form where I input the order information, as well as add as many items as I want. If I click the "Add another item" button, a new set of form elements will be added to input the new data, amounts, etc..
I could hack this out in pure javascript, but I'm pretty sure there has to be a more magical, railsish way to do it, maybe with a partial view or something. I'm just a little too new to rails to know what it is.
What is the best way to dynamically add the new form elements, and then to access them in the create controller?
Can't beat this Railscasts.com tutorial provided by Ryan Bates.
Episode 196: Nested Model Form, pt. 1
Here's an example that works with just a single level of nesting
Models
models/company.rb
class Company < ActiveRecord::Base
has_many :people, :dependent => :destroy
accepts_nested_attributes_for :people, :allow_destroy => true
end
models/person.rb
class person < ActiveRecord::Base
belongs_to :company
end
Controllers
companies_controller.rb
def new
#company = Company.new
3.times { person = #company.people.build }
end
Views
views/companies/_form.html.erb
<% form_for #company do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :people do |builder| %>
<%= render "people_fields", :f => builder %>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
views/companies/_people_fields.html.erb
<p>
<%= f.label :name, "Person" %>
<%= f.text_field :name %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove" %>
</p>

Resources