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
Related
Let's say I have 2 models, Box.rb and Toy.rb. Rails gives me pages and methods to create new toys and boxes independently. However, I would like to ensure/provide the ability to create a box's first toy when creating a box for the first time.
In line with DRY, I wanted to simply put <%= render "Toy/form" %> inside the _form.html.erb file that is made for Box.
The problem is that Toy's _form file contains, well, a form_for method for obvious reasons. It's a problem for what I am trying to do because I will end up with 1 form nested in the other, while all I really want is to get the Toy fields while keeping to DRY…?
You should be using accepts_nested_attributes_for along with fields_for.Assuming your associations are like this
#box.rb
Class Box < ActiveRecord::Base
has_many :toys
accepts_nested_attributes_for :toys
end
#toy.rb
Class Toy < ActiveRecord::Base
belongs_to :box
end
#box_controller.rb
def new
#box = Box.new
#toy = #box.toys.build
end
def create
#box = Box.new(box_params)
if #box.save
-----
else
-----
end
end
private
def box_params
params.require(:box).permit(:box_attribute_1,:box_attribute_2,:more_box_attributes, toys_attributes: [:toy_attribute_1,:more_toy_attributes])
end
In your box/form,you can do like this
<%= form_for(#box) do |f| %>
<div class="field">
<%= f.label :box_attribute_1 %><br />
<%= f.text_field :box_attribute_1 %>
</div>
<div class="field">
<%= f.label :box_attribute_2 %><br />
<%= f.text_field :box_attribute_2 %>
</div>
<div class="field">
<%= f.label :box_attribute_3 %><br />
<%= f.text_field :box_attribute_3 %>
</div>
<%= f.fields_for #toy do |builder| %>
<%= render 'toys/form', :f => builder %>
<% end %>
<% end %>
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 %>
Before asking this, I have already tried with the solution detailed here, but it didn't work.
context: 2 models, Idea and Project. 1 idea belongs to 1 project, 1 projects has many ideas.
I want to create a form to create ideas that have fields of ideas but also to specify which Project they belong to, by indicating the project_id field. I'm doing it with accepts_nested_attributes_for
Issue: I'm not being able to grab the project_id when creating a new idea from a form. From the console I see that a new idea has been saved, but project_id for that idea always returns nil
code:
ideas_controller.rb
# GET /ideas/new
def new
#idea = Idea.new
#idea.build_project
respond_to do |format|
format.html # new.html.erb
format.json { render json: #idea }
end
models> idea.rb
class Idea < ActiveRecord::Base
belongs_to :project
accepts_nested_attributes_for :project
mount_uploader :picture, PictureUploader
validates :name, presence: true, allow_blank: false
end
_form.html.erb
<%= form_for(#idea) do |f| %>
<% f.fields_for :project do |project_fields| %>
<% if #idea.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#idea.errors.count, "error") %> prohibited this idea from being saved: </h2>
<ul>
<% #idea.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :picture %><br>
<%= f.file_field :picture %>
</div>
<div class="field">
<%= f.label :project %><br>
<%= f.number_field :project_id, :class=>"Number" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<% end %>
"Nested attributes allow you to save attributes on associated records through the parent", according to the documentation. you are doing it through the child, which I don't think is possible.
So either move the accepts_nested_attributes_for to the parent model and use the parent's form to create the parent fields along with the child field. so you'll be creating project and idea through the projects_controller and projects/_form.html.erb
or opt out for something else.
without using accepts_nested_attributes_for, add project_ids to your ideas params attributes as an empty array:
def idea_params
params.require(:idea).permit(:name, :project_ids => [])
end
app/views/ideas/_form.html.erb:
<div>
<%= idea.label 'File under at least one project' %>
<% Project.all.order(name: :asc).each do |project| %>
<div>
<%= check_box_tag "idea[project_ids][]", project.id %>
<%= project.name %>
</div>
</div>
this code will give you checkboxes of your projects to select. this means first you have to create the projects separely in project's own controller and form. so you are not using nesting attributes.
Okay so you can see my models and controller below. What I want to be able to do is when a user is adding a new lease, the user can select from a list of the properties already in the database for the property_id, and also select from a list of the tenants on file for the tenant_id. I am relatively new to rails and really don't have a clue how I would go about doing this. In my controller I put #property = Property.all #tenant = Tenant.all to make them accessible but I don't know how to leverage them in the way that I want.
Lease Model
class Lease < ActiveRecord::Base
attr_accessible :lease_end, :lease_start, :property_id, :tenant_id
belongs_to :tenant
belongs_to :property
end
Property Model
class Property < ActiveRecord::Base
attr_accessible :address_line_1, :city, :county, :image, :rent
belongs_to :lease
end
Tenant Model
class Tenant < ActiveRecord::Base
attr_accessible :email, :name, :phone
belongs_to :lease
end
Lease controller methods for adding a new lease and editing a lease
def new
#lease = Lease.new
#property = Property.all
#tenant = Tenant.all
respond_to do |format|
format.html # new.html.erb
format.json { render json: #lease }
end
end
# GET /leases/1/edit
def edit
#lease = Lease.find(params[:id])
#property = Property.all
#tenant = Tenant.all
end
EDIT:
Drop down box work but the options arn't wahat I want. I am getting options like # for tenants and # for properties. I would like if I could get the tenants names and the address of the properties instead
_form file code updated with teresko's suggestions
<%= form_for(#lease) do |f| %>
<% if #lease.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#lease.errors.count, "error") %> prohibited this lease from
being saved:</h2>
<ul>
<% #lease.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :tenant_id %><br />
<%= f.select :tenant, options_for_select(#tenants) %>
</div>
<div class="field">
<%= f.label :property_id %><br />
<%= f.select :property, options_for_select(#properties) %>
</div>
<div class="field">
<%= f.label :lease_start %><br />
<%= f.date_select :lease_start %>
</div>
<div class="field">
<%= f.label :lease_end %><br />
<%= f.date_select :lease_end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
First tip : the good practice is to use #properties when you have many properties, not #property. Idem with #tenants.
Then, you can set this in the new or edit page :
<% form_for #lease do |f| %>
<%= f.select :property, options_for_select(#properties) %>
<%= f.select :tenant, options_for_select(#tenants) %>
<% end %>
Next tip is to use a partial named app/views/leases/_form.html.erb to set the previous form, when the new and edit are rendering the same form. Then, your new and edit views will become
<%= render :partial => 'form' %>
To have specific option display, you can read the options_for_select or options_from_collection_for_select docs
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-options_for_select
<%= f.select :property, options_from_collection_for_select(#properties, 'id', 'name') %>
Choose the best method to your case.
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