I have some trouble to fill my associated objects.
I have associated object, i try to fill my local DB's tables with form data, i can fill the building and all the object which composed itself : light_reseller, stage, furniture, location. But i don't find how to fill the component "points" of stage and
furniture !
here is my model building.rb
class Building < ActiveRecord::Base
has_many :stage, dependent: :destroy
has_many :furniture, dependent: :destroy
has_one :light_reseller, dependent: :destroy
has_one :location, dependent: :destroy
accepts_nested_attributes_for :light_reseller, :location, :stage, :furniture, :allow_destroy => true
end
class LightReseller < ActiveRecord::Base
belongs_to :building
end
class Location < ActiveRecord::Base
belongs_to :building
end
class Stage < ActiveRecord::Base
belongs_to :building
has_many :points, dependent: :destroy
accepts_nested_attributes_for :points
end
class Furniture < ActiveRecord::Base
belongs_to :building
has_many :points, dependent: :destroy
accepts_nested_attributes_for :points
end
class Point < ActiveRecord::Base
belongs_to :furniture
belongs_to :stage
end
I use a form to fill my objects :
<%= form_for (#building) do |f| %>
<% if #building.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#building.errors.count, "error") %> prohibited this store from being
saved:</h2>
<ul>
<% #building.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<h2>Store</h2>
<%= f.label :name %><br>
<%= f.text_field :name, class: "form-control" %>
<%= f.fields_for :location do |loc| %>
<%= loc.label :address %><br>
<%= loc.text_field :address, class: "form-control" %>
<%= loc.label :city %><br>
<%= loc.text_field :city, class: "form-control" %>
<% end %>
<%= f.fields_for :light_reseller do |lr| %>
<%= lr.label :"light_reseller name" %><br>
<%= lr.text_field :name, class: "form-control" %>
<% end %>
<h2>Stage</h2>
<%= f.fields_for :stage do |ft| %>
<%= ft.label :name %><br>
<%= ft.text_field :name, class: "form-control" %>
<%= ft.fields_for :point do |pt| %>
<%= pt.label :"point value" %><br>
<%= pt.text_field :val, class: "form-control" %>
<% end %>
<h3>Stage entries</h3>
<%= ft.fields_for :entrie do |et| %>
<%= et.label :"entrie name" %><br>
<%= et.text_field :name, class: "form-control" %>
<%= et.fields_for :point do |ept| %>
<%= ept.label :"point value" %><br>
<%= ept.text_field :val, class: "form-control" %>
<% end %>
<% end %>
<% end %>
<%= f.fields_for :furniture do |ft| %>
<h2>Furniture</h2>
<%= ft.label :name %><br>
<%= ft.text_field :name, class: "form-control" %>
<%= ft.fields_for :point do |pt| %>
<%= pt.label :"point value" %><br>
<%= pt.text_field :val, class: "form-control" %>
<% end %>
<%= ft.fields_for :point do |pt| %>
<%= pt.label :"point value" %><br>
<%= pt.text_field :val, class: "form-control" %>
<% end %>
<% end %>
</div>
<div class="actions">
<%= f.submit "Add a Store", class: "btn btn-default"%>
</div>
<% end %>
and here is my controller :
class BuildingsController < ApplicationController
before_action :set_building, only: [:show, :edit, :update, :destroy]
def index
#buildings = Building.all
end
def new
#building = Building.new
#building.build_location
#building.build_light_reseller
#building.furniture.new
#building.stage.build
end
def show
end
def create
#building = Building.new(buildings_params)
respond_to do |format|
if #building.save
format.html { redirect_to #building, notice: 'building was successfully created.' }
format.json { render :index, status: :created, location: #building }
else
format.html { render :new }
format.json { render json: #building.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #building.update(buildings_params)
format.html { redirect_to #building, notice: 'building was successfully updated.' }
format.json { render :show, status: :ok, location: #buildings }
else
format.html { render :edit }
format.json { render json: #buildings.errors, status: :unprocessable_entity }
end
end
end
def destroy
#building.destroy
respond_to do |format|
format.html { redirect_to buildings_path, notice: 'building was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_building
#building = Building.find(params[:id])
end
def buildings_params
params.require(:building).permit(:name, location_attributes:[:address, :city], light_reseller_attributes:[:name], furniture_attributes:[:name, point_attributes:[:val]], stage_attributes:[:name, point_attributes:[:val]])
end
So when i submit my form, my controller receive these data:
"building"=>{"name"=>"Fnac", "location_attributes"=>{"address"=>"12 bird road", "city"=>"Paris"}, "light_reseller_attributes"=>{"name"=>"Marc"}, "stage_attributes"=>{"0"=>{"name"=>"First", "point"=>{"val"=>"1235"}}}, "furniture_attributes"=>{"0"=>{"name"=>"shelf", "point"=>{"val"=>"45"}}}}
You have a relation has_many :points with furniture and stage, so you need to change
<%= ft.fields_for :point do |pt| %>
to
<%= ft.fields_for :points do |pt| %>
And also change point_attributes to points_attributes in your buildings_params.
Try the below changes too along with the above.
class Building < ActiveRecord::Base
has_many :stages, dependent: :destroy
has_many :furnitures, dependent: :destroy
has_one :light_reseller, dependent: :destroy
has_one :location, dependent: :destroy
accepts_nested_attributes_for :light_reseller, :location, :stages, :furnitures, :allow_destroy => true
end
def new
#building = Building.new
#building.build_location
#building.build_light_reseller
#building.furnitures.build
#building.stages.build
end
def buildings_params
params.require(:building).permit(:name, location_attributes:[:address, :city], light_reseller_attributes:[:name], furnitures_attributes:[:name, points_attributes:[:val]], stages_attributes:[:name, points_attributes:[:val]])
end
<%= f.fields_for :stage do |ft| %> to <%= f.fields_for :stages do |ft| %>
and
<%= f.fields_for :furniture do |ft| %> to <%= f.fields_for :furnitures do |ft| %>
Related
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]
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.
I would like to combine both values :hours and :minutes and convert them to to_i in seconds. Next is to assign this value (which should be in seconds) to the :time_duration which is a column in the cars db before it creates a new service. The :time_duration is in a hidden_field because there's no reason to render this data in the view.
views
This is my _car_fields.html.erb which is a nested partial inside a view template called, _form.html.erb .
_car_fields.html.erb
<div class="nested-fields">
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :hours %>
<%= f.select :hours, '0'..'8' %>
<%= f.label :minutes %>
<%= f.select :minutes, options_for_select( (0..45).step(15), selected: f.object.minutes )%><br>
<%= f.label :price %><br>
<%= f.text_field :price, :value => (number_with_precision(f.object.price, :precision => 2) || 0) %> <br>
<%= f.label :details %><br>
<%= f.text_area :details %></div>
<%= link_to_remove_association "Remove Car", f, class: 'btn btn-default' %>
<%= f.hidden_field :time_duration, value: %>
<br>
<hr>
</div>
_form.html.erb
<%= simple_form_for #service do |f| %>
<div class="field">
<%= f.label "Select service category" %>
<br>
<%= collection_select(:service, :service_menu_id, ServiceMenu.all, :id, :name, {:prompt => true }) %>
<%= f.fields_for :cars do |task| %>
<%= render 'car_fields', :f => task %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'Add New Car', f, :cars, class: 'btn btn-default' %>
</div><br>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
controller
services_controller
def new
#service = current_tech.services.build
end
def create
#service = current_tech.services.build(service_params)
respond_to do |format|
if #service.save
format.html { redirect_to #service, notice: 'Service was successfully created.' }
format.json { render :show, status: :created, location: #service }
else
format.html { render :new }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
private
def service_params
params.require(:service).permit(:name, :service_menu_id, cars_attributes: [:tech_id, :name, :hours, :minutes, :price, :details, :_destroy])
end
models
service.rb
class Service < ActiveRecord::Base
belongs_to :tech
belongs_to :service_menu
has_many :cars, dependent: :destroy
accepts_nested_attributes_for :cars, :reject_if => :all_blank, :allow_destroy => true
end
car.rb
class Car< ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
end
First, you can remove the hidden time_duration field from the form, since it is not needed.
Then, you'll create a before_save method for your car model:
car.rb
class Car < ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
before_save :generate_time_duration
def generate_time_duration
self[:time_duration] = hours.hours.to_i + minutes.minutes.to_i
end
end
What this does: Before the car object is saved, it will run the generate_time_duration method. What this method does is it simply sums the hours.to_i and minutes.to_i and assigns it to the car's time_duration attribute.
Update old DB records
Since you're adding this functionality in your application AFTER records have already been created, here is a quick way to update all of your current records:
In your command line, open a rails console by running the command rails c (or rails console)
In the console, run this command: Car.all.each { |c| c.save! }
This is a quick, one-time fix that will loop through all Car records, save them, and subsequently update their time_duration fields.
I am trying to build a form for a has_many :through relationship. The Problem is, that I can access the attributes from the join table (:through) in the form, but not the other table.
My models look like this:
class Recipe < ActiveRecord::Base
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities
end
class Quantity < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
class Ingredient < ActiveRecord::Base
has_many :quantities
has_many :recipes, through: :quantities
end
My Form:
<%= form_for(#recipe) do |f| %>
<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="quantities">
<%= f.fields_for :quantities do |builder| %>
<%= render 'quantity_fields', :f => builder%>
<% end %>
<%= link_to_add_association 'Add', f, :quantities %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And the quantity_fields partial:
<p>
<%= f.label :value, "Value" %>
<%= f.text_field :value %>
<%= f.label :unit, "Unit" %>
<%= f.text_field :unit %>
<%= f.text_field :ingredient %>
<%= f.fields_for :ingredient do |builder|%>
<%= builder.label :name, "Ingredient"%>
<%= builder.text_field :name %>
<% end %>
</p>
The fields for the quantities attributes are correct, but the text field for the ingredient name (within the quantity_fields partial) stays empty.
On the Rails console on the other hand I can easily use the ingredient method on a quantity object and get a result.
I try to find a solution to this since a while now, and it becomes really frustrating to me. I am sure this is a standard task and I am just missing a small part. Can anyone help me?
Edit:
Also when I change my recipe form to directly render the ingredients, it doesn't work. The text fields remain empty.
<%= form_for(#recipe) do |f| %>
<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="ingredients">
<%= f.fields_for :ingredients do |builder| %>
<p>
<%= builder.label :name, "Name" %>
<%= builder.text_field :name %>
<%= builder.label :description, "Description" %>
<%= builder.text_field :description %>
</p>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Edit 2:
The recipes controller:
class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :edit, :update, :destroy]
# GET /recipes
# GET /recipes.json
def index
#recipes = Recipe.all
end
# GET /recipes/1
# GET /recipes/1.json
def show
end
# GET /recipes/new
def new
#recipe = Recipe.new
end
# GET /recipes/1/edit
def edit
end
# POST /recipes
# POST /recipes.json
def create
#recipe = Recipe.new(recipe_params)
respond_to do |format|
if #recipe.save
format.html { redirect_to #recipe, notice: 'Recipe was successfully created.' }
format.json { render :show, status: :created, location: #recipe }
else
format.html { render :new }
format.json { render json: #recipe.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /recipes/1
# PATCH/PUT /recipes/1.json
def update
respond_to do |format|
if #recipe.update(recipe_params)
format.html { redirect_to #recipe, notice: 'Recipe was successfully updated.' }
format.json { render :show, status: :ok, location: #recipe }
else
format.html { render :edit }
format.json { render json: #recipe.errors, status: :unprocessable_entity }
end
end
end
# DELETE /recipes/1
# DELETE /recipes/1.json
def destroy
#recipe.destroy
respond_to do |format|
format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_recipe
#recipe = Recipe.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def recipe_params
params.require(:recipe).permit(:title, :description, quantities_attributes: [:value, :unit, :recipe, :ingredient, :_destroy])
end
end
When utilizing a has_many :through association in that way, you can set up your models like this:
class Recipe < ActiveRecord::Base
has_many :quantities
has_many :ingredients, inverse_of: :recipes
accepts_nested_attributes_for :quantities
end
class Quantity < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredients
end
class Ingredient < ActiveRecord::Base
has_many :quantities
has_many :recipes, inverse_of: :ingredients
end
This article covers nested attributes with has_many :through associations in a similar context. Hope this helps!
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