Ruby on Rails, two models in one form - ruby-on-rails

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!!!

Related

Rails: fields_for not working

I was following this RailsCast about Nested Model Form, but something seems weird.
Here is Model relationship
class Question < ActiveRecord::Base
belongs_to :survey
end
class Survey < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions
end
_form.html.erb(created by scaffold)
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<% f.fields_for :questions do |builder| %>
<div class ='question'>
<%= builder.label :content, "Question" %>
<br>
<%= builder.text_area :content, :rows => 3 %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
survey_controller.rb
# GET /surveys/new
def new
#survey = Survey.new
3.times do |i|
#survey.questions.build
end
end
I was expecting it will display like
However,it didn't display 3 blocks of question.
You've missed a very small but important part of the form - actually outputting the f.fields_for call.
You need to call <%= f.fields_for :question (note the =) to actually output the generated form.

How can I render a _form file from a different controller without having conflicting form_for's?

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 %>

Rails controller saving from different table

I'm new to rails to bear with me.
This concerns two of my models: Product and Manufacturer.
When creating a new product the user can select which manufacturer the product belongs to from a drop down list. The problem is that I can't get this manufacturer to save.
I know I have to add some code to the controller and I've tried various things but to no avail.
Here's the view:
<h1>New Product</h1>
<%= form_for(#product) do |f| %>
<div>
<%= f.label :name, 'Name' %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :market_price, 'Market Price' %>
<%= f.text_field :market_price %>
</div>
<div>
<%= f.label :sell_price, 'Sell Price' %>
<%= f.text_field :market_price %>
</div>
<div>
<%= f.label :stock_level, 'Stock Level' %>
<%= f.text_field :stock_level %>
</div>
<div>
<%= f.label :manufacturer, 'Manufacturer' %>
<%= f.collection_select(:manufacturer, Manufacturer.all, :id, :name, prompt: true) %>
</div>
<div>
<%= f.label :location, 'Location' %>
<%= f.collection_select(:location, Product.all, :id, :location, prompt: true) %>
</div>
<br> </br>
<div>
<%= f.submit "Create Product" %>
</div>
And here's part of the controller:
def create
#product = Product.new(params[:product].permit(:name, :market_price, :sell_price, :stock_level, :location))
#product.save
flash[:notice] = 'Product Created'
redirect_to #product
end
end
After hours of trying several nesting methods, I still can't get this to work.
Surely it's very common to save fields from various models on one page???
You would usually nest your routes, so that products were within manufacturer:
resources :manufacturer do
resources :products
end
Then your form would be a form for an array:
form_for([#manufacturer, #product]) do |f|
f.hidden_field :manufacturer_id, value: #manufacturer.id
This allows you to pass in the ID of both the manufacturer and the product.
Now in your controller you can use something like the following, provided the associations are set up, such as product belongs_to :manufacturer and manufacturer has_many :products
#manufacturer = find(params[:manufacturer_id])
#product = #manufacturer.products.create()
In your form, you need to have a field for manufacturer_id, not manufacturer. You would change f.collection_select(:manufacturer to be f.collection_select(:manufacturer_id.
Then, in your controller, you need to add manufacturer_id to the list of parameters you are permitting in your permit method call. So it would be Product.new(params[:product].permit(:name, :market_price, :sell_price, :stock_level, :location, :manufacturer_id)).

accepts_nested_attributes_for issue in Form

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.

How to store select information

I am a bit unsure on how to store the data from a collection_tag.
Here the model I am working on
Customer GenderManager Genders
Id Id Id
First Customer_id Description
Last Gender_id
Email Created_at
Password
Here the relationship
Customer::class
has_many :gender_managers
accepts_nested_attributes_for :gender_managers, allow_destroy: :true
Gender::class
has_many :gender_managers
GenderManagers:class
belongs_to :customer_id
belongs_to :gender_id
The form I have currently his has follow
<%= form_for #customer do |f| %>
<div class="customer_edit_error_message">
<%= render 'shared/error_messages' %>
</div>
<fieldset>
<div class="customer_edit_name">
<%= f.label :first_name %>
<%= f.text_field :first_name %>
</div>
<div class="customer_edit_middle_name">
<%= f.label :middle_name %>
<%= f.text_field :middle_name %>
</div>
<div class="customer_edit_last_name">
<%= f.label :last_name %>
<%= f.text_field :last_name %>
</div>
<div class="customer_edit_gender">
<%= f.collection_select(:customer, :id, Gender.all, :id, :description) %>
</div>
<div class="customer_edit_button">
<%= f.submit "Save changes", class: "button_accept" %>
</div>
<% end %>
In customerController not sure if I must do something. I know its not right its giving me errors. Its the form that worry me on the select like
Thanks in advance.
I recommend you add an attribute gender to your Customer model. If you want to keep track of changes you can create a GenderHistory model and do something like this in Customer:
class Customer < ActiveRecord::Base
has_many :gender_histories
before_update :track_gender
def track_gender
self.gender_histories.create(:gender => self.gender) if self.gender_changed?
end
end
Your Genders and GenderManager models are not necessary.

Resources