I am stuck up with building nested forms using Ruby on Rails.
I am trying to build a form which has fields from three tables(User, Contact, Address). User table has address_id and contact_id. When user fills the details, contact details should be saved in contact table and address should be saved in address table. Both the ids should get saved in user table along with the user details. How should I proceed?
My model,
class Address < ApplicationRecord
has_one :user
end
class Contact < ApplicationRecord
has_one :user
end
class User < ApplicationRecord
belongs_to :address
belongs_to :contact
end
My controller,
class UsersController < ApplicationController
def new
#user = User.new
#user.build_contact
#user.build_address
end
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:name, :email, contact_attributes: [:phone], address_attributes: [:street, :city])
end
end
And my view is,
<%= form_for(user) do |f| %>
<% if user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<%= f.fields_for :contact do |c| %>
<div class="field">
<%= c.label :phone %>
<%= c.text_field :phone %>
</div>
<% end %>
<%= f.fields_for :address do |a| %>
<div class="field">
<%= a.label :street %>
<%= a.text_field :street %>
</div>
<div class="field">
<%= a.label :city %>
<%= a.text_field :city %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Is my approach right? Kindly please suggest. Thanks in advance.
You missing a couple of lines...
class User < ApplicationRecord
belongs_to :address
belongs_to :contact
accepts_nested_attributes_for :address
accepts_nested_attributes_for :contact
end
Also ensure you accept :id and :_delete
params.require(:user).permit(:name, :email, contact_attributes: [:id, :phone, :_delete], address_attributes: [:id, :street, :city, :_delete]
Related
Following the DRY rule, I've inserted the render partial command inside my officers\_form.html.erb view:
<%= form_for(officer) do |f| %>
<% if officer.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(officer.errors.count, "error") %> prohibited this officer from being saved:</h2>
<ul>
<% officer.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= render :partial => 'users/form', :locals => {:user => #officer.user} %>
<%= render :partial => 'addresses/form', :locals => {:address => #officer.address} %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
This is my users\_form.html.erb file:
<%= form_for(user) do |f| %>
<% if user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.fields_for :user do |user_fields| %>
<div class="field">
<%= user_fields.label :last_name %>
<%= user_fields.text_field :last_name %>
</div>
<div class="field">
<%= user_fields.label :first_name %>
<%= user_fields.text_field :first_name %>
</div>
<div class="field">
<%= user_fields.label :middle_name %>
<%= user_fields.text_field :middle_name %>
</div>
<div class="field">
<%= user_fields.label :gender %>
<%= user_fields.select(:gender, User.genders.keys) %>
</div>
<% end %>
<!--div class="actions"-->
<!--%= f.submit %-->
<!--/div-->
<% end %>
Same reasoning as for User code applies to Addresses code, so I'll omit here for shortness.
This is my officers_controller file:
class OfficersController < BaseController
before_action :set_officer, only: [:show, :edit, :update, :destroy]
# GET /officers
# GET /officers.json
def index
#officers = Officer.all
end
# GET /officers/1
# GET /officers/1.json
def show
end
# GET /officers/new
def new
#officer = Officer.new
end
# GET /officers/1/edit
def edit
end
# POST /officers
# POST /officers.json
def create
#officer = Officer.new(officer_params)
respond_to do |format|
if #officer.save
format.html { redirect_to #officer, notice: 'Officer was successfully created.' }
format.json { render :show, status: :created, location: #officer }
else
format.html { render :new }
format.json { render json: #officer.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /officers/1
# PATCH/PUT /officers/1.json
def update
respond_to do |format|
if #officer.update(officer_params)
format.html { redirect_to #officer, notice: 'Officer was successfully updated.' }
format.json { render :show, status: :ok, location: #officer }
else
format.html { render :edit }
format.json { render json: #officer.errors, status: :unprocessable_entity }
end
end
end
# DELETE /officers/1
# DELETE /officers/1.json
def destroy
#officer.destroy
respond_to do |format|
format.html { redirect_to officers_url, notice: 'Officer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_officer
#officer = Officer.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def officer_params
#params.fetch(:officer, {})
params.require(:officer).permit!
end
end
Now if I go to http://localhost:3000/officers/new, the parts included in both the users and addresses forms are shown, but when I press the Create officer button nothing happens. Where is the error?
class Officer < ApplicationRecord
belongs_to :manager#, inverse_of: :officer
has_many :customers#, inverse_of: :officer
has_one :user, as: :userable, dependent: :destroy
has_one :address, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :user, :address
end
class Manager < ApplicationRecord
has_many :officers#, inverse_of: :manager
has_one :user, as: :userable, dependent: :destroy
has_one :address, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :user, :address
end
class User < ApplicationRecord
enum gender: { female: 0, male: 1, undefined: 2 }
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :userable, polymorphic: true
end
Thanks,
FZ
You have not set user_attributes in your officer_params, do this:
def officer_params
#params.fetch(:officer, {})
params.require(:officer).permit(:id, user_attributes: [:id, :last_name, :middle_name, :first_name, :gender, :_destroy])
end
And also change accepts_nested_attributes_for :user, :address
to
'accepts_nested_attributes_for :user, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :address, reject_if: :all_blank, allow_destroy: true'
And you need to address_attributes to your officer params aswell but since i don't know your database field i can't do that part for you but it's pretty much the same as the user_attributes but with different fields(except :id and :_destroy which are the same for all).
EDIT:
This is a nested form:
<%= form_for(officer) do |f %>
<%= f.fields_for :user do |user| %>
<%= user.text_field :last_name %>
<%= user.text_field :middle_name %>
<%= user.text_field :first_name %>
<% end %>
<%= f.fields_for :address do |address| %>
<%= address.text_field :street_name %>
<%= address.text_field :zip_code %>
<% end %>
<%= f.submit 'submit' %>
This way one submit button supplies for all the nested forms aswell.
What you have is this:
<%= form_for(officer) do |f %>
<%= form_for(user) do |f|
<%= f.fields_for :user do |user| %> // this (f) now stands for the user form instead of the officer form
<%= user.text_field :last_name %>
<%= user.text_field :middle_name %>
<%= user.text_field :first_name %>
<% end %>
<% end %>
<%= form_for(address) do |f| %>
<%= f.fields_for :address do |address| %> // same for this one
<%= address.text_field :street_name %>
<%= address.text_field :zip_code %>
<% end %>
<% end %>
<%= f.submit 'submit' %>
Now you don't have a nested form, you just have 3 different full forms and you can't submit multiple forms with one submit button this way.
The new topic form has fields for title, description, and first post content.
Upon submission, a topic with values for title, description, user id, and forum id should be created, along with a post with values for content, user id, and topic id. However, the post's :content is not getting saved to the table, though user id and topic id are.
views/topics/_form.html.erb
<%= form_for(#topic) do |f| %>
<% if #topic.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#topic.errors.count, "error") %> prohibited this topic from being saved:</h2>
<ul>
<% #topic.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:forum] %><input type="hidden" id="topic_forum_id" name="topic[forum_id]" value="<%= params[:forum] %>" /><% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<textarea name="post[content]" class="form-control" cols="80" rows="20"><%= #post.content %></textarea>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
controllers/topics_controller.rb
def new
#topic = Topic.new
#post = Post.new
end
def create
user_id = current_user.id || 1 # temporary assignment until guest account generated
#topic = Topic.new(title: params[:topic][:title], description: params[:topic][:description], forum_id: params[:topic][:forum_id], user_id: user_id)
if #topic.save
#post = Post.new(content: params[:post][:content], topic_id: #topic.id, user_id: user_id)
if #post.save
flash[:notice] = "Successfully created topic."
redirect_to "/topics/#{#topic.id}"
else
render action: 'new'
end
else
render action: 'new
end
end
models/post.rb
class Post < ActiveRecord::Base
belongs_to :topic
belongs_to :user
has_many :replies, :dependent => :nullify
validates :content, presence: true
attr_accessor :content
end
models/topic.rb
class Topic < ActiveRecord::Base
belongs_to :forum
belongs_to :user
has_many :posts, :dependent => :destroy
validates :title, presence: true, length: { maximum: 255 }
validates :user_id, presence: true
validates :forum_id, presence: true
end
I would implement the concept of accepsts_nested_attributes like below to handle your situation.
#topic.rb
class Topic < ActiveRecord::Base
belongs_to :forum
belongs_to :user
has_many :posts, :dependent => :destroy
accepts_nested_attributes_for :posts
validates :title, presence: true, length: { maximum: 255 }
validates :user_id, presence: true
validates :forum_id, presence: true
end
#topics_controller.rb
def new
#topic = Topic.new
#post = #topic.posts.build
end
def create
user_id = current_user.id || 1 # temporary assignment until guest account generated
#topic = Topic.new(params[:topic])
if #topic.save
#post = Post.new(params[:post])
#post.topic_id = #topic.id
#post.user_id = user_id
if #post.save
flash[:notice] = "Successfully created topic."
redirect_to #topic
else
render action: 'new'
end
else
render action: 'new'
end
end
#form
<%= form_for(#topic) do |f| %>
<% if #topic.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#topic.errors.count, "error") %> prohibited this topic from being saved:</h2>
<ul>
<% #topic.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:forum] %><input type="hidden" id="topic_forum_id" name="topic[forum_id]" value="<%= params[:forum] %>" /><% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<%= f.fields_for #post do |p| %>
<div class="field">
<%= p.text_area :content, cols: 80, rows: 20, class: "form-control" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
i have 2 model book and wishlist.
and between this 2 model i use many to many relationship.
Below is my model.
class Book < ActiveRecord::Base
has_many :book_wishlist_customizations, dependent: :destroy
has_many :wish_lists ,through: :book_wishlist_customizations
end
class BookWishlistCustomization < ActiveRecord::Base # through table
belongs_to :wish_list
belongs_to :book
end
class WishList < ActiveRecord::Base
has_many :book_wishlist_customizations
has_many :books,through: :book_wishlist_customizations
end
when i delete book from admin side it raise following error.
PG::ForeignKeyViolation: ERROR: update or delete on table "books" violates foreign key constraint "fk_rails_7a6b92667b" on table "wish_lists" DETAIL: Key (id)=(1) is still referenced from table "wish_lists". : DELETE FROM "books" WHERE "books"."id" = $1.
below is my book form.
<%= form_for #book, url: books_path do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :stream_id %>
<%= f.collection_select :stream_id, Stream.all, :id, :stream_name, prompt: true %>
</div>
</br>
<div class="field">
<%= f.label :university_board_id, "University" %>
<%= f.collection_select :university_board_id, UniversityBoard.where(category_id: $college_id).all, :id, :name, prompt: true %>
</div>
</br>
<div class="field">
<%= f.label :course_standard_id, "Course & Year" %>
<%=f.select(:course_standard_id, :"Please select" => true)%>
<%=f.select(:year_semester, :"Please select" => true)%>
</div>
</br>
<div class="field">
<%= f.label :college_school_id, "College" %>
<%=f.select(:college_school_id, :"Please select" => true)%>
</div>
</br>
<div class="field">
<%= f.label :subject_id, "Subject" %>
<%=f.select(:subject_id, :"Please select" => true)%>
</div>
</br>
<div class="field">
<%= f.label :book_name, "Book name" %>
<%= f.text_field :book_name %>
</div>
</br>
<div class="actions">
<%= f.submit "Continue", class: "btn btn-primary" %>
</div>
<% end %>
below is my controller:-
class BooksController < ApplicationController
def new
#book = Book.new
$college_id = Category.where(category_name: ['college']).select(:id)
$school_id = Category.where(category_name: ['school']).select(:id)
end
def create
if user_signed_in?
#book = Book.new(book_params)
#last_commision = Commision.last
#book_status = BookStatus.find_by(status_name: "pending")
#book.update_attributes( commision_id: #last_commision.id, book_status_id: #book_status.id, user_id: current_user.id, quantity: "1")
if #book.save
# exit
session[:book_id] = #book.id
# session[:photo_id] = #book_photo.id
redirect_to multi_steps_path
else
respond_to do |format|
format.html { render :new }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
else
session[:book_details] = book_params
redirect_to unauthenticated_root_path, notice: "For post your book first login in our system."
end
end
def stream_change_course
#stream_details=CourseStandard.where(stream_id: params[:stream], category_id: $college_id)
render :json => #stream_details
end
def course_change_college_subject
#year_semester=CourseStandard.where(id: params[:course])
#subject_details=Subject.where(course_standard_id: CourseStandard.where(category_id: $college_id))
#college_details=CollegeSchool.where(course_standard_id: CourseStandard.where(category_id: $college_id))
render :json => {:subject => #subject_details,:college => #college_details,:year =>#year_semester}
end
def price_calculate_commission
#final_amount=params[:price].to_i-(params[:price].to_i*Commision.last.percentage)/100
render :json => #final_amount
end
private
def book_params
params.require(:book).permit(:book_id, :user_id, :book_name, :book_condition_id,:price_for_sale, :mrp, :book_auther, :isbn, :description, :publish_year, :edition, :publication, :book_status_id, :quantity, :category_id, :university_board_id, :college_school_id, :course_standard_id, :subject_id, :commision_id, :stream_id, :medium_id,:year_semester) rescue {}
# params.require(:book).permit(:stream_id, :university_board_id, :course_standard_id, :subject_id, :id)
end
def book_photo_params
params.require(:book_photo).permit(:photo) rescue {}
end
end
I'm guessing from your error that your WishList model has a field for book ids. It shouldn't. Your BookWishListCustomization model's book id field and wishlist id field along with the belongs_to and has_many associations your models have now are enough.
I know there is a lot questions like this before, I have following all the answer, but still mine doesn't work. please help.
survey.rb
# app/models/survey.rb
class Survey < ActiveRecord::Base
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:questions].blank? }, :allow_destroy => true
end
question.rb
# app/models/question.rb
class Question < ActiveRecord::Base
belongs_to :survey
end
surveys_controller.rb
# app/controllerss/surveys_controller.rb
def new
#survey = Survey.new
#survey.questions.build
end
def edit
end
def create
#survey = Survey.new(survey_params)
respond_to do |format|
if #survey.save
format.html { redirect_to #survey, notice: 'Survey was successfully created.' }
format.json { render :show, status: :created, location: #survey }
else
format.html { render :new }
format.json { render json: #survey.errors, status: :unprocessable_entity }
end
end
end
def survey_params
params.require(:survey).permit(:name, questions_attributes: [:id, :content, :_destroy])
end
_form.html.erb
# app/views/surveys/_form.html.erb
<%= nested_form_for #survey do |f| %>
<% if #survey.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#survey.errors.count, "error") %> prohibited this survey from being saved:</h2>
<ul>
<% #survey.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>
<%= f.fields_for :questions do |builder| %>
<div class="field">
<%= builder.label :content, "Question" %> <br>
<%= builder.text_field :content, :rows => 3 %>
<%= builder.link_to_remove "Remove this question" %>
</div>
<% end %>
<p><%= f.link_to_add "Add a question", :questions %></p>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Help? what do I miss?
:reject_if => lambda { |a| a[:questions].blank? }
a variable is a hash of attributes which will be passed to a question record. Your question model has no questions field, hence a[:questions] is always blank and the record it is rejected. Instead, do:
:reject_if => :all_blank
I have the following Models
cities(id, name, geo {lng,lat})
geo(lng,lat)
Cities Model
class City
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :timezone, type: String
field :slug, type: String
belongs_to :region
belongs_to :country
embeds_one :geo_location
accepts_nested_attributes_for :geo_location
end
Geo Locations Model
class GeoLocation
include Mongoid::Document
include Mongoid::Timestamps
field :lng, type: String
field :lat, type: String
embedded_in :city
end
Cities Controller
class CitiesController < ApplicationController
before_action :set_city, only: [:show, :edit, :update, :destroy]
# GET /cities
def index
#cities = City.all
end
# GET /cities/1
def show
end
# GET /cities/new
def new
#city = City.new
#regions = Region.all.asc(:name)
#countries = Country.all.asc(:name)
end
# GET /cities/1/edit
def edit
#regions = Region.all.asc(:name)
#countries = Country.all.asc(:name)
end
# POST /cities
def create
#city = City.new(city_params)
if #city.save
redirect_to #city, notice: 'City was successfully created.'
else
render action: 'new'
end
end
# PATCH/PUT /cities/1
def update
if #city.update(city_params)
redirect_to #city, notice: 'City was successfully updated.'
else
render action: 'edit'
end
end
# DELETE /cities/1
def destroy
#city.destroy
redirect_to cities_url, notice: 'City was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_city
#city = City.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def city_params
params.require(:city).permit(:name, :timezone, :region_id, :country_id, :slug, :geo_locations_attributes => [:id, :lag, :lat])
end
end
Form:
<%= form_for(#city) do |f| %>
<% if #city.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#city.errors.count, "error") %> prohibited this city from being saved:</h2>
<ul>
<% #city.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :country %><br>
<%= f.collection_select :country_id, #countries, :id, :name, :prompt => "Please Select" %>
</div>
<div class="field">
<%= f.label :region %><br>
<%= f.collection_select :region_id, #regions, :id, :name, :prompt => "Please Select" %>
</div>
<div class="field">
<%= f.label :timezone %><br>
<%= f.text_field :timezone %>
</div>
<div class="field">
<%= f.label :slug %><br>
<%= f.text_field :slug %>
</div>
<%= f.fields_for :geo_locations do |geo_location| %>
<div class="field">
<%= geo_location.label :lag %><br>
<%= geo_location.text_field :lag %>
</div>
<div class="field">
<%= geo_location.label :lat %><br>
<%= geo_location.text_field :lat %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
New View
<h1>New city</h1>
<%= render 'form' %>
<%= link_to 'Back', cities_path %>
The ERROR I am getting
Unpermitted parameters: geo_location
On controller, Replace your city_params method with this,
def city_params
params.require(:city).permit(:name, :timezone, :region_id, :country_id, :slug, :geo_location_attributes => [:id, :lag, :lat])
end
On view, Replace this "f.fields_for :geo_locations" with "f.fields_for :geo_location"
Problem in geo_locations_attributes. It should be geo_location_attributes as this is one-to-one relationship.