accepts_nested_attributes_for issue in Form - ruby-on-rails

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.

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.

rails 4 nested form fields_for are not displayed

I just started learning Rails 4.2. The problem is that one field in the form is not being displayed.
I have restaurant, category and a dish. While creating a dish, the category and restaurant will also be inputted via /dishes/new.
Expected behaviour: Dish, Category and Restaurant fields are displayed.
Actual behaviour: Only Dish and Category fields are displayed.
Here are my models
models/restaurant.rb
class Restaurant < ActiveRecord::Base
has_many :categories
has_many :dishes, :through => :categories
end
models/category.rb
class Category < ActiveRecord::Base
belongs_to :restaurant
has_many :dishes
end
models/dish.rb
class Dish < ActiveRecord::Base
belongs_to :category
validates :name, :price, :category, :restaurant, :presence => true
accepts_nested_attributes_for :restaurant, :category
end
dish controller
def new
# I think this is where
# I am making a mistake
#dish = Dish.new
category = #dish.build_category
restaurant = category.build_restaurant
end
def create
#dish = Dish.new(dish_params)
respond_to do |format|
if #dish.save
.... # default stuff #
end
end
end
# strong params
def dish_params
params.require(:dish).permit(:name, :description, :price, restaurant_attributes: [:name], category_attributes: [:name])
end
Dishes views/dishes/_form.html.erb
<%= form_for(#dish) do |f| %>
<% if #dish.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#dish.errors.count, "error") %> prohibited this dish from being saved:</h2>
<ul>
<% #dish.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :nameWoW %><br>
<%= f.text_area :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.number_field :price %>
</div>
*** The restaurant name field is not being displayed **
<%= f.fields_for :restaurant do |restaurant| %>
<div class="field">
<%= restaurant.label :Restname %><br>
<%= restaurant.text_area :name %>
</div>
<% end %>
<%= f.fields_for :category do |category| %>
<div class="field">
<%= category.label :Catname %><br>
<%= category.text_area :name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I have followed steps from rails guide, browsed questions on stackoverflow and read some blog posts as well but havent been able to figure out whats wrong. Some micro level mistake is blocking me :( . Anyone knows whats wrong ?
Thanks in advance.
UPDATE:
Hey I found a solution.
def new
#dish = Dish.new
#dish.build_category
#dish.category.build_restaurant
end
This works well.But this is just a part of the actual solution. I had to do lot of /dish/create controller modification as well. I think the entire solution will have to be put in blog post. Otherwise it wont make any sense. I will soon be posting and updating it here.
You can add this in your dish.rb
class Dish
delegate :restaurant, to: :category
end
Or you can do
<%= f.fields_for :restaurant, #dish.category.restaurant do |restaurant| %>
<div class="field">
<%= restaurant.label :Restname %><br>
<%= restaurant.text_area :name %>
</div>
<% end %>
I think you are missing:
class Dish
belongs_to :restaurant, through: :category
end
You have it on the other side (many) but not there. You could test this by trying to output #dish.restaurant on your form (should be empty but not nil).
def new
# I think this is where
# I am making a mistake
#dish = Dish.new
category = #dish.category.build
restaurant = category.restuarant.build
end

How create new instance of a model in relation many to many?

I try to create a furniture object, which is in relation by a has_many_and_belongs_to with stores, this is my model:
class Furniture < ActiveRecord::Base
attr_accessible :area, :description, :name, :size
has_and_belongs_to_many :stores
end
My problem is that I don't know how create a new furniture, because i try to associate furniture with one or more store with check box, but I obtain this error: undefined method merge for #<Store:0x007ff16ae27e40>.
These are my view with form and my controller with new and create action:
View:
<%= form_for #furniture do |f| %>
<%= f.label :name %>
<%= f.text_field :name %> <br><br>
<%= f.label :description %>
<%= f.text_field :description %> <br><br>
<%= f.label :size %>
<%= f.text_field :size %> <br><br>
<% #store.each do |store| %>
<div>
<%= f.check_box :stores, store %>
<%= store.name %>
</div>
<% end %>
<%= f.submit %>
<% end %>
Controller:
def new
#furniture = Furniture.new
#store = Store.order('name ASC')
end
def create
#furniture = Furniture.create(params[:furniture])
redirect_to admins_path
end
How can I solve it?? Have you some suggestion to create a new object with this relation ship??
Thank you very much
EDIT:
I have a join table between furniture and store
The has_and_belongs_to association adds a method collection_singular_ids= that for the current case will be #furniture.store_ids=. According to the docs
The collection_singular_ids= method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
So, you can use this idea to add the stores to your furniture. Replace
<% #store.each do |store| %>
<div>
<%= f.check_box :stores, store %>
<%= store.name %>
</div>
<% end %>
with
<% #store.each do |store| %>
<div>
<%= f.check_box :store_ids, {:multiple => true}, store.id, nil %>
<%= store.name %>
</div>
<% end %>

Ruby on Rails, two models in one form

I have two very similar models Pretreatment and Diagnosis, that belong to the model Patient:
class Pretreatment < ActiveRecord::Base
belongs_to :patient
attr_accessible :content
end
class Diagnosis < ActiveRecord::Base
belongs_to :patient
attr_accessible :content
end
class Patient < ActiveRecord::Base
attr_accessible :age, :name, :city, :street, :number
has_many :anamneses
has_many :befunds
end
On the Patient show page I'm displaying two forms, one for the Preatreatment and another for the Diagnosis:
<%= form_for([#patient, #patient.preatreatments.build]) do |f| %>
<div class="field">
<%= f.label :conten %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= form_for([#patient, #patient.diagnosiss.build]) do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My question is how can I bring the two forms together, so that the user only has to press once the submit button? Im not sure but I think nested attributes is not the right thing to handle it, maybe thefields_for` tag?
Update I tried to use fields_for tag:
<%= form_for([#patient, #patient.pretreatment.build]) do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<%= fields_for([#patient, #patient.diagnosiss.build]) do |u| %>
<div class="field">
<%= u.label :content %><br />
<%= u.text_field :content %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
But I get the error:
undefined method `model_name' for Array:Class in <%= fields_for([#patient,#patient.befunds.build]) do |u| %>
Use fields_for for the associated models.
There should be no square brackets arround the parameters of fields_for
In your code example, I cannot find the relation between Patient and Diagnosis, and the plural of diagnosis is diagnoses, you can specify this in config/initializers/inflections.rb:
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'diagnosis','diagnoses'
end
So your Patient model should contain
class Patient < ActiveRecord::Base
attr_accessible :age, :name, :city, :street, :number
has_many :diagnoses
end
And you can write in your form:
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<%= fields_for(#patient, #patient.diagnoses.build) do |u| %>
<div class="field">
<%= u.label :content %><br />
<%= u.text_field :content %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
You can achieve that using nested attributes :
patient.rb
class Patient < ActiveRecord::Base
attr_accessible :age, :name, :pretreatments_attributes, :diagnosiss_attributes
has_many :pretreatments
has_many :diagnosiss
accepts_nested_attributes_for :pretreatments
accepts_nested_attributes_for :diagnosiss
end
patients_controller.rb
def show
#patient = Patient.find(params[:id])
#patient.pretreatments.build
#patient.diagnosiss.build
respond_to do |format|
format.html # show.html.erb
format.json { render json: #patient }
end
end
patients/show.html.erb:
<%= form_for #patient do |f|%>
<h3>Pretreatments:</h3>
<%= f.fields_for :pretreatments do |field| %>
<%= field.label "Content" %></div>
<%= field.text_field :content %>
<% end %>
<h3>Diagnosis:</h3>
<%= f.fields_for :diagnosiss do |field| %>
<%= field.label "Content" %></div>
<%= field.text_field :content %>
<% end %>
<%=f.submit %>
<% end %>
And that all
There are a few ways of doing this:
The way the fields_for works is you have a form_for for the parent model and within it you can place fields_for the models which belong to the parent. A good example is given in the Rails Docs
A simple and more extensible option over time but not very "semantic" one would be to use JavaScript/JQuery. You could trigger $("form #new_pretreatments").submit(); and the same for the Diagnosis once a button is clicked.
Maybe instead you could have just one model to store them both. For example a model called Disease. Each time a patient gets a disease a new one is created and it has columns for each the Diagnosis and Pretreatment. It's probably the best option instead of adding another model for each bit of info a patient can have.
You can use fields_for for the second model, which works like form_for but doesn't generate the form tags. See the docs.
There are some gems available for nested forms. one of them is awesome_nested_fields. I haven't used this earlier but that shows good code in documentation. Another one is simple_form.
Hope that helps!!!

Using pre-existing records to add a new record to the database in rails

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.

Resources