Nested form attributes - ruby-on-rails

I have one form working to save in the database but I want to save some fields that are going to be saved in a different record.
<%= form_for #complaint, url: {action: 'create'}, :html => {:multipart => true} do |f| %>
<%= f.text_field :complaint_info %>
<%= f.fields_for :witness do |witnesses_form| %>
<%= witnesses_form.text_field :name %>
<% end %>
<% end %>
In my controller:
def new
#complaint = Complaint.new
end
def create
#complaint = current_user.complaints.build(complaint_params)
if #complaint.save
redirect_to dashboard_complaint_path(#complaint)
else
render 'new'
end
end
private
def complaint_params
params.require(:complaint).permit(:complaint_info, witnesses_attributes: [:name])
end
on the model:
class Complaint < ActiveRecord::Base
belongs_to :user
has_many :witnesses
accepts_nested_attributes_for :witnesses
end
.
class Witness < ActiveRecord::Base
belongs_to :complaint
end
But I'm getting this error:
Unpermitted parameter: witness
Everything seems to be as it suppose to be, what am I missing here?
EDIT:
was able to save the record by adding:
#complaint.witnesses.build
to the create action in the controller but it is still not letting me save the :name there
<ActiveRecord::Relation [#<Witness id: 1, name: nil, phone: nil, complaint_id: 8, created_at: "2015-06-08 20:05:06", updated_at: "2015-06-08
EDIT 2:
Was able to fix it by moving the #complaint.witnesses.build from the create action to the new action and it fixed it, now I can create the record and lets me save the text_fields in it.

Can you try with changing your controller and views codes as following
In controller
def new
#complaint = Complaint.new
#witnesses = #complaint.witnesses.build
end
def edit
#witnesses = #complaint.witnesses
end
In views
<%= f.fields_for :witnesses, #witnesses do |witnesses_form| %>
<%= witnesses_form.text_field :name %>
<% end %>

I was able to fix it by adding #complaint.witnesses.build to the newaction instead of the create action.
So my controller now looks like this:
def new
#complaint = Complaint.new
#complaint.witnesses.build
end
def create
#complaint = current_user.complaints.build(complaint_params)
if #complaint.save
redirect_to dashboard_complaint_path(#complaint)
else
render 'new'
end
end
private
def complaint_params
params.require(:complaint).permit(:complaint_info, witnesses_attributes: [:name])
end

Related

Rails: "Validation failed: Class must exist" in a Form_with

I've a multiple relation table named Order which belongs_to a Relai(to avoid singular/plurials complications), a Customer and a Composition. I set my Order model accordingly with nested_attributes as mentioned below. Before adding the customer part, I want to send a mail with just the #composition and a #relai chose with a dropdown.
class Order < ApplicationRecord
belongs_to :relai
belongs_to :composition
accepts_nested_attributes_for :relai
accepts_nested_attributes_for :composition
end
I set my OrdersController to get the :composition_id from my params
def new
#order = Order.new
#composition = Composition.find(params[:composition_id])
#relais = Relai.all
end
def create
#florist = Florist.first
#composition = Composition.find(params[:composition_id])
##relai = Relai.find(params[:relai_id]) # If I add this line it returns "Couldn't find Relai without an ID"
#order = Order.new(order_params)
if #order.save!
raise
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(
florist_attributes: [:id],
relai_attributes: [:id, :name, :address],
composition_attributes: [:id, :name]
)
end
My View:
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai, #relais, :id, :name %>
<%= compo.fields_for :composition do |fc| %>
<%= fc.collection_select :composition, #compositions, :id, :name %>
<% end %>
# Start Block that doesn't display the Relai.all
#<%#= compo.fields_for :relai do |fr| %>
#<%#= fr.label :relai, 'Ici la liste des relais :' %>
#<%#= fr.association :relai, collection: #relais %>
#<%#= fr.association :relai, #relais, :id, :name %>
#<%# end %>
# End Block
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>
And the routes:
resources :compositions, only: [:show, :index] do
resources :orders, only: [:show, :new, :create, :index]
end
I also tried:
"nested_attributes" which seems to not works (I can't see my Relai collection in view.
=> https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes)
the "optional: true" in model which throw me an error of:
PG::NotNullViolation: ERROR: null value in column "relai_id" of relation "orders" violates not-null constraint
Can someone explain me why I got a "Validation failed: Relai must exist, Composition must exist" whereas these appears in my params?
{"authenticity_token"=>"[FILTERED]", "order"=>{"relai"=>"2"}, "commit"=>"MAIL", "composition_id"=>"3"}
I'm on Rails 6.1.4 ; Ruby 2.6.6
accepts_nested_attributes_for works from parent to children. You are using it on the child side (Order).
If you just need to assign an existing Relai and Composition to Order just use a select for both of them:
class Order < ApplicationRecord
belongs_to :relai
belongs_to :composition
end
def new
#order = Order.new
#compositions = Composition.all
#relais = Relai.all
end
def create
#order = Order.new(order_params)
if #order.save!
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:relai_id, :composition_id)
end
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai_id, #relais, :id, :name %>
<%= compo.collection_select :composition_id, #compositions, :id, :name %>
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>
EDIT: Setting Composition on the controller.
def new
composition = Composition.find(params[:composition_id])
#order = Order.new(composition: composition)
#relais = Relai.all
end
def create
#order = Order.new(order_params)
if #order.save!
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:relai_id, :composition_id)
end
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai_id, #relais, :id, :name %>
<%= compo.hidden_field :composition_id %>
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>

Saving a list of emails from a form-text input into Models email_list attribute (array)

My goal is to when adding a new product with the new product form, to have an input where one can add a list of emails separated by a space. The list of emails in this string field would be saved as an array of emails in the email_list array attribute of the Product model. This way each product has many emails. (later an email will be sent to these users to fill out questionaire, once a user fills it out there name will be taken off this list and put on completed_email_list array.
I am relatively new to rails, and have a few questions regarding implementing this. I am using postgresql, which from my understanding I do not need to serialize the model for array format because of this. Below is what I have tried so far to implement this. These may show fundamental flaws in my thinking of how everything works.
My first thinking was that I can in my controllers create action first take params[:email].split and save that directly into the email_list attribute (#product.email_list = params[:email].split. It turns out that params[:email] is always nil. Why is this? (this is a basic misunderstanding I have)(I put :email as accepted param).
After spending a long time trying to figure this out, I tried the following which it seems works, but I feel this is probably not the best way to do it (in the code below), which involves creating ANOTHER attribute of string called email, and then splitting it and saving it in the email_list array :
#product.email_list = #product.email.split
What is the best way to actually implement this? someone can clear my thinking on this I would be very grateful.
Cheers
Products.new View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.input :email %>
<%= f.button :submit %>
<%end %>
Products Controller
class ProductsController < ApplicationController
before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
if params[:category].blank?
#products= Product.all.order("created_at DESC")
else
#category_id=Category.find_by(name: params[:category]).id
#products= Product.where(:category_id => #category_id).order("created_at DESC")
end
end
def new
#product=current_user.products.build
#categories= Category.all.map{|c| [c.name, c.id]}
end
def show
end
def edit
#categories= Category.all.map{|c| [c.name, c.id]}
end
def update
#product.category_id = params[:category_id]
if #product.update(product_params)
redirect_to product_path(#product)
else
render 'new'
end
end
def destroy
#product.destroy
redirect_to root_path
end
def create
#product=current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = #product.email.split
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:product).permit(:title, :description, :category_id, :video, :thumbnail,:email, :email_list)
end
def find_product
#product = Product.find(params[:id])
end
end
To solve your original issue
#product.email_list = params[:email].split. It turns out that params[:email] is always nil
:email is a sub key of :product hash, so it should be:
#product.email_list = params[:product][:email].split
Demo:
params = ActionController::Parameters.new(product: { email: "first#email.com last#email.com" })
params[:email] # => nil
params[:product][:email] # => "first#email.com last#email.com"
I'd say that what you have is perfectly fine, except for the additional dance that you're doing in #product.email_list=#product.email.split, which seems weird.
Instead, I'd have an emails param in the form and an #emails= method in the model (rather than email and #email=):
def emails=(val)
self.email_list = val.split
end
Alternatively, you could do that in the controller rather than having the above convenience #emails= method, similar to the way you're handling the category_id:
#product = current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = product_params[:emails].split
Because you need validations on your emails and to make it cleaner I would create an email table, make Product table accept Email attribues and use cocoon gem to have a nice dynamic nested form with multiple emails inputs.
1) models
class Product < ActiveRecord::Base
has_many :emails, dependent: :destroy
accepts_nested_attributes_for :emails, reject_if: :all_blank, allow_destroy: true
end
class Email < ActiveRecord::Base
belong_to :product
validates :address, presence: true
end
2) Controller
class ProductsController < ApplicationController
def new
#product = current_user.products.build
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:project).permit(:title, :description, :category_id, :video, :thumbnail, emails_attributes: [:id, :address, :_destroy])
end
end
3) View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.association :category %>
<div id="emails">
<%= f.simple_fields_for :emails do |email| %>
<%= render 'emails_fields', f: email %>
<div class="links">
<%= link_to_add_association 'add email', f, :emails %>
</div>
<%= end %>
</div>
<%= f.button :submit %>
<% end %>
In your _emails_fields partial:
<div class="nested-fields">
<%= f.input :address %>
<%= link_to_remove_association "Remove email", f %>
</div>
Then setup cocoon's gem and javascript and you'll be good.
Reference: https://github.com/nathanvda/cocoon

"One-to-Many" Rails Form Creation

I have an association of One Classroom has Many Students. I want to create a form where I can create a student and assign him a classroom. And I am having problems creating the form.
model/classroom.rb
class Classroom < ActiveRecord::Base
has_many :students
end
model/student.rb
class Student < ActiveRecord::Base
belongs_to :classroom
end
I want to create a new student and assign it to a certain classroom.
<%= form_for(#student) do |f|%>
<%= f.label :name %>
<%= f.text_field :name %>
<br />
<br />
<%= f.label :student.classroom.number %> #Is this correct?
<%= f.text_field :student.classroom.number %> #Is this correct?
<%= f.submit %>
<%end%>
The attributes for each model are
1.9.3-p448 :026 > Classroom
=> Classroom(id: integer, number: string, created_at: datetime, updated_at: datetime)
1.9.3-p448 :027 > Student
=> Student(id: integer, name: string, created_at: datetime, updated_at: datetime, classroom_id: string)
students_controller
class StudentsController < ApplicationController
def index
#students = Student.all
end
def show
#student = Student.find(params[:id])
end
def new
#student = Student.new
end
def create
#student = Student.new(article_params)
respond_to do |format|
if #student.save
format.html {redirect_to(#student, notice: 'Student was successfully created.')}
else
format.html {render action: "new"}
end
end
end
private
def article_params
params.require(:student).permit(:name, :classroom_id)
end
end
classroom_controller
class ClassroomsController < ApplicationController
def index
#classrooms = Classroom.all
end
def show
#classroom = Classroom.find(params[:id])
end
def new
#classroom = Classroom.new
end
def create
#classroom = Classroom.new(article_params)
respond_to do |format|
if #classroom.save
format.html {redirect_to(#classroom, notice: 'Classroom was successfully created.')}
else
format.html {render action: "new"}
end
end
end
private
def article_params
params.require(:classroom).permit(:number)
end
end
You can set a hidden field setting it to the classroom itself, if you already know which classroom you want to add him in:
<%= f.hidden_field, :classroom_id, value: here_you_put_the_classroom_id $>
And don't forget to add :classroom_id in the permitted params in your controller.
Another way you can do if you want the option to select the classroom you are putting the student in, you can create a select field passing all the classrooms.
<% classroom_array = Classroom.all.map { |classroom| [classroom.name, classroom.id] } %>
<%= options_for_select(classroom_array) %>
Don't forget to add the permitted params again.
Hope it helps.
***UPDATE***
The options_for_select should go inside de select tag, like this:
<% classroom_array = Classroom.all.map { |classroom| [classroom.number, classroom.id] } %>
<%= f.label :classroom %>
<%= f.select(:classroom_id, options_for_select(classroom_array)) %>
***UPDATE 2***
Pluck could also be an option, as long as you pass the classroom id as param. So, the code can be refactored to:
<%= f.label :classroom %>
<%= f.select :classroom_id, Classroom.all.pluck(:name, :id) %>
Assuming you want to assign the Student to an existing Classroom:
First, ensure the Student model has the following in the attr_accessible:
attr_accessible :classroom_id
In your form, instead of your second label/text_field, you should then be able to do:
<%= f.label :classroom %>
<%= f.select(:classroom_id, Classroom.all.pluck(:number)) %>
Note that for the f.select method you must pass the attribute you are setting, not the association name (i.e., classroom_id not classroom)
Also note that best practice would be to move logic associated with collecting information from a model (i.e. Classroom.all.pluck(:number)) into an instance variable in the controller,
e.g. #classrooms = Classroom.all.pluck(:number)
and using that #classrooms instance variable in your view instead.
Aside from the above, you should also read some more about symbols. What you've tried there with :student.classroom.number isn't going to work how you thought it might. There's a good SO question about it here: When to use symbols instead of strings in Ruby?

Rails form No Method Error

I'm trying to put a simple form together, but I keep getting a no method error whenever I insert a form field into the html.
What's wrong here?
The Error:
undefined method `name' for #<Upload id: nil, created_at: nil, updated_at: nil>
new.html.erb
<h1>Uploads#new</h1>
<p>Find me in app/views/uploads/new.html.erb</p>
<% form_for :upload, :url => uploads_path do |f| %>
<p>
Name: <%= f.text_field :name %>
</p>
<p><%= submit_tag "Create Upload" %></p>
<% end %>
upload.rb
class Upload < ActiveRecord::Base
has_many :tasks
end
uploads_controller.rb
class UploadsController < ApplicationController
def index
#uploads = Upload.find(:all)
end
def new
#upload = Upload.new
end
def create
#upload = Upload.new(params[:project])
if #upload.save
flash[:notice] = "Film successfully uploaded"
redirect_to uploads_path
else
render :action => 'new'
end
end
end
From looking at the error, your upload model does not appear to have a name attribute. If you've added this, the maybe you have forgotten to migrate you database?

Associating two records after create in Rails

I'm working on an association between two models:
class Person < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :person
end
Many person records exist in the system that don't necessarily correspond to a user, but when creating a user you need to either create a new person record or associate to an existing one.
What would be the best way to associate these two models when the person record already exists? Do I need to manually assign the user_id field, or is there a Rails way of doing that?
Where #user is a recently created user, and #person is an existing person.
#user.person = #person
#user.save
Alternately:
User.new :person => #person, ... #other attributes
or in params form:
User.new(params[:user].merge({person => #person}))
As far as forms go:
<% form_for #user do |f| %>
...
<% fields_for :person do |p| %>
<%= p.collection_select, :id, Person.all, :id, :name, :include_blank => "Use fields to create a person"%>
<%= p.label_for :name%>
<%= p.text_field :name %>
...
<% end %>
<% end %>
And in the user controller:
def create
#user = User.create(params[:user])
#person = nil
if params[:person][:id]
#person = Person.find(params[:person][:id])
else
#person = Person.create(params[:person])
end
#user.person = #person
...
end
If you don't want to create/alter a form for this, you can do
#person_instance.user = #user_instance
For has_many relationships, it would be:
#person_instance.users << #user_instance
You first have to do a nested form :
<% form_for #user do |user| %>
<%= user.text_field :name %>
<% user.fields_for user.person do |person| %>
<%= person.text_field :name %>
<% end %>
<%= submit_tag %>
<% end %>
In your User model :
class User < ActiveRecord::Base
accepts_nested_attributes_for :person
end
If you want the person deleted when the user is :
class User < ActiveRecord::Base
accepts_nested_attributes_for :person, :allow_destroy => true
end
And in your controller do nothing :
class UserController < ApplicationController
def new
#user = User.new
#find the person you need
#user.person = Person.find(:first)
end
def create
#user = User.new(params[:user])
#user.save ? redirect_to(user_path(#user)) : render(:action => :new)
end
end

Resources