How to handle nested forms in Rails? - ruby-on-rails

Hello all I am using cocoon gem to build nested forms.
has_many :filled_cartridges, inverse_of: :request, dependent: :destroy
accepts_nested_attributes_for :filled_cartridges, allow_destroy: true
This is my request model.
Inside request.html.erb I have:
<%= f.fields_for :filled_cartridges do |filled_cartridge| %>
<fieldset class="nested-fields">
<%= filled_cartridge.autocomplete_field :cartridge_name, autocomplete_cartridge_cartridge_name_requests_path, :update_elements => {} %>
<%= filled_cartridge.hidden_field :_destroy %>
<%= link_to_remove_association "удалить", f %>
</fieldset>
<% end %>
Strong Params:
def request_params
params.require(:request).permit(:name, :address,:filled_cartridges_attributes => [:client_id,:cartridge_name,:cartridge_id,
:request_id,:count,:fill_date,:_destroy,:id],)
end
So I have destroy and id params in order to be able destroy records.
I want to handle my request and filled cartridges model in one instance. I believe I can do that thanks to accepts_nested_attributes.
The problem is that it cant save filled_Cartridge:
SQLite3::ConstraintException: NOT NULL constraint failed: filled_cartridges.client_id
I guess the problem is inside the controller method:
#request = Request.new( request_params )
#client = Client.where("name = ?", #request.name).take
if params[:request][:filled_cartridges_attributes].present? and params[:stype_ids].present?
if params[:stype_ids].include? '1'
params[:request][:filled_cartridges_attributes].each do |_,value|
if #client.present?
value[:client_id] = #client.id
else
value[:client_id] = 0
end
value[:request_id] = 0 #временный id
#cartridge = Cartridge.where('cartridge_name=?', "#{value[:cartridge_name]}").take
if #cartridge.nil?
#cartridge = Cartridge.create!(:cartridge_name => "#{value[:cartridge_name]}")
end
value[:cartridge_id] = #cartridge.id
value[:cartridge_name] = #cartridge.cartridge_name
end
end
end
NOTE: Can i set params like value[:parm] = something

The problem has nothing to do with your nested forms, it has to do with a not null constraint in your SQLite schema. Either pass in your client_id to validate the constraint or remove the constraint from the db

Related

rails form with validation always failing

I'm stuck on this... Asked a few questions already, but can't get my head around this.
I have a form for adding bibliography (model Biblio) that has a simple validation field on title of the bibliography.
Validation always fails, even when valid data is added.
MODEL
class Biblio < ApplicationRecord
# validates_presence_of :auteurs => there's a nested form too but
# I commented it out in order to isolate the problem
accepts_nested_attributes_for :auteurs
validates :titre, presence: true
CONTROLLER
(full text and I didn't translate in order to avoid typos)
def new
#biblio = Biblio.new(params_biblio)
#biblio.auteurs.build
end
def nouveau
# this method renders 'nouveau.html.erb',
# that contains the form allowing the addition of bibliography
#biblio = Biblio.new
if params[:id] # id is an optional parameter
#auteur = Auteur.find(params[:id])
#idauteur = #auteur.id
end
end
def ajouter
# is the method that treats the post form that was sent
#biblio = Biblio.new
if #biblio.save
# the 4 following lines are irrelevant here as they only add the
# second and subsequent authors to the join table.
# No validation and works fine.
b = auteurs_devises(params[:biblio][:auteurs])
aut = b.map do |var|
lett = Auteur.find(var)
lett.biblios << #biblio
end
redirect_to voir_biblio_url(Biblio.last)
else
if params[:id]
#auteur = Auteur.find(params[:id])
#idauteur = #auteur.id
end
render 'nouveau'
end
end
THE VIEW:
<%= form_for :biblio, url: administration_ajoute_biblio_url do |f| %>
<%= f.fields_for :auteurs do |aut| %>
<%= aut.label t('auteur') %>
<%= aut.text_field :nom , :name =>"biblio[auteurs][nom]", data: {autocomplete_source: auteurs_enum_path} %>
<% end %>
<%= f.label t('titre').capitalize %>
<%= f.text_field :titre %>
These are the params that are sent to the method nouveau:
Started POST "/administration/biblios/nouveau" for ::1 at 2017-02-07 21:28:28 +0100
Processing by Administration::BibliosController#ajouter as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+354h4M0Tg+BX21XAuQ6YMKS0BGQ8UjET8paKjkGCBsS1up1lB131KsoaCy563X4juDz0EJy46WgXbHcu51Kgw==", "biblio"=>{"auteurs"=>{"nom"=>"Godding"}, "titre"=>"Test Tuesday Evening", "soustitre"=>"", "recueil"=>"", "editeur"=>"", "annee"=>"", "isbn"=>"", "genre"=>"source", "revue_id"=>"", "revue_no"=>"", "revue_page"=>"", "lieu"=>"", "commentaire"=>""}, "commit"=>"Enregistrer"}
(0.2ms) BEGIN
(0.1ms) ROLLBACK
Rendering administration/biblios/nouveau.html.erb within layouts/messources
CONTROLLER AGAIN
def params_biblio
params.require(:biblio).permit(
:titre,
:soustitre,
:editeur,
:isbn,
:recueil,
:genre,
:revue_id,
:revue_no,
:revue_page,
:annee,
:lieu,
:commentaire,
auteurs: [:nom] )
end
For the sake of completeness, here's my routes.rb:
# ADMINISTRATION => BIBLIOGRAPHIE
get 'biblios/nouveau(/:id)' => 'biblios#nouveau', as: 'nouvelle_biblio'
post 'biblios/nouveau(/:id)' => 'biblios#ajouter', as: 'ajoute_biblio'
delete 'biblios/supprime/:id' => 'biblios#supprime', as: 'supprime_biblio'
get 'biblios/maj/:id' => 'biblios#cherche_maj', as: 'maj_biblio'
patch 'biblios/maj/:id' => 'biblios#maj', as: 'patch_maj_biblio'
I must be blind. I'm doing something wrong... I put a title to this bibliography ('Test Tuesday Evening'), this is the only field on which I left a validation, and despite this, validation always fails.

How to handle data from nested forms in Rails 4 with cocoon gem?

I am using Cocoon gem to do nested forms.
I have models like that:
# request.rb
has_many :filled_cartridges, inverse_of: :request, dependent: :destroy
accepts_nested_attributes_for :filled_cartridges, :reject_if => :all_blank, allow_destroy: true
#filled_cartridge.rb
belongs_to :request
Inside of my form_for #request i have nested form like that:
<div id="filled_cartridges">
<%= f.fields_for :filled_cartridges do |filled_cartridge| %>
<%= render 'filled_cartridge_fields', f: filled_cartridge %>
<% end %>
<div class="links">
<%= link_to_add_association 'add', f, :filled_cartridges %>
</div>
Where filled_cartridge_fields partial is like that:
<fieldset>
<%= f.text_field :cartridge_id %>
<%= f.hidden_field :_destroy %>
<%= link_to_remove_association "remove", f %>
</fieldset>
When i click on "add" it adds one more . When clicking on "remove" it removes that .
When i submit form the params for nested form look like that:
filled_cartridges_attributes: !ruby/hash:ActionController::Parameters
'0': !ruby/hash:ActionController::Parameters
cartridge_id: '12'
_destroy: 'false'
'1429260587813': !ruby/hash:ActionController::Parameters
cartridge_id: '2'
_destroy: 'false'
How do i access these params, and how to save them. How to traverse over these params and save them, or do Cocoon gem has some built in functionality? And finally how to check if these params are set? Since it is nested, it tricks me.
EDIT: My request_controllers#create:
def create
#request = Request.new( request_params )
# code for handling Request model
# here i want to handle nested model too (filled_cartridge)
#request.save
if #request.save
flash[:success] = "Заявка была добавлена"
redirect_to #request
else
render 'new'
end
end
EDIT2: my strong params:
def request_params
params.require(:request).permit(:name, :address, :phone, :mobile, :type, :description, :priority, :responsible, :price, :payed, :date, filled_cartridges_attributes: [:cartridge_id, :_destroy], :stype_ids => [], :social_media =>[])
end
In a recent project using cocoon I had to access the params of the attributes about to be saved. I figured a code in my create action in my controller. The trick is to understand how to retrieve the key of the hash of the attribute that is about to be saved. The key of the hash is that number '1429260587813' that is in your params
...
'1429260587813': !ruby/hash:ActionController::Parameters
cartridge_id: '2'
_destroy: 'false'
So you need to create a loop in your create action to retrieve this key using ruby hash method "keys". I do a loop because when using cocoon dynamic nested field I might create more than one nested attributes at once so it means more than one key to retrieve.
Here is a the code that worked for me, read my comments which explains the different steps of this code. I hope it will help you to adapt it to your needs.
#Here I just initialize an empty array for later use
info_arr = []
#First I check that the targeted params exist (I had this need for my app)
if not params[:recipe]["informations_attributes"].nil?
#z variable will tell me how many attributes are to be saved
z = params[:recipe]["informations_attributes"].keys.count
x = 0
#Initiate loop to go through each of the attribute to be saved
while x < z
#Get the key (remember the number from above) of the first hash (params) attribute
key = params[:recipe]["informations_attributes"].keys[x]
#use that key to get the content of the attribtue
value = params[:recipe]["informations_attributes"][key]
#push the content to an array (I had to do this for my project)
info_arr.push(value)
#Through the loop you can perform actions to each single attribute
#In my case, for each attributes I creates a new information association with recipe
#recipe.informations.new(title: info_arr[x]["title"]).save
x = x +1
end
end
This work to access cocoon nested attribute content and apply actions based on your need. This worked for me so you should be able to use this sample code and adapt it to your need.

Rails 4 NOT updating nested attributes

Issue: Instead of updating nested attributes, they are being created on top of the existing nested attributes when I hit the #update action of the associated features_controller.rb
Likely Cause: I think the problem lies in my lack of understanding in Rails' form_for. I think the breakdown is in my views, how I render the persisting nested attributes, and/or how I fail to specify the nested attribute's id, causing it to simply create a new one
feature.rb
class Feature < ActiveRecord::Base
...
has_many :scenarios
accepts_nested_attributes_for :scenarios,
allow_destroy: true,
reject_if: :all_blank
...
end
features_controller.rb
def update
...
project = Project.find(params[:project_id])
#feature = Feature.find(params[:id])
if #feature.update_attributes(feature_params)
# checking feature_params looks good...
# feature_params['scenarios'] => { <correct object hash> }
redirect_to project
else
render :edit
end
end
...
private
def feature_params
params.require(:feature).permit(:title, :narrative, :price, :eta, scenarios_attributes[:description, :_destroy])
end
_form.html.haml (simplified)
= form_for [#project, #feature] do |f|
...
- if #feature.new_record? -# if we are creating new feature
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
- else -# if we are editing an existing feature
= f.fields_for :scenarios do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
I'm sure there's a nicer way to achieve the if #feature.new_record? check. I'm also using a few Javascript hooks to create dynamic nested attribute forms (which I've left out), heavily influenced by Railscast #196 Nested Model Form (revised)
I would love a really nice Rails-y implementation of dealing with these sorts of nested forms.
Try adding :id to the :scenario_attributes portion of your feature_params method. You only have the description field and the ability to allow a destroy.
def feature_params
# added => before nested attributes
params.require(:feature).permit(:id, :title, :narrative, :price, :eta, scenarios_attributes => [:id, :description, :_destroy])
end
As #vinodadhikary suggested, you no longer need to check if feature is a new record, since Rails, specifically using the form_for method, will do that for you.
Update:
You don't need to define if #feature.new_record? ... else in your form. It will be taken care by Rails when you use form_for. Rails checks if the action is going to be create or update based on object.persisted?, so, you can update your form to:
= form_for [#project, #feature] do |f|
...
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
As #Philip7899 mentioned as a comment in the accepted answer, allowing the user to set the id means that they could "steal" children records belonging to another user.
However, Rails accepts_nested_attributes_for actually checks the id and raises:
ActiveRecord::RecordNotFound:
Couldn't find Answer with ID=5 for Questionnaire with ID=5
Basically the ids are looked for in the children association (again, as said by #glampr). Therefor, the child record belonging to another user is not found.
Ultimately, 401 is the response status (unlike the usual 404 from ActiveRecord::RecordNotFound)
Follows some code I used to test the behaviour.
let :params do
{
id: questionnaire.id,
questionnaire: {
participation_id: participation.id,
answers_attributes: answers_attributes
}
}
end
let :evil_params do
params.tap do |params|
params[:questionnaire][:answers_attributes]['0']['id'] = another_participant_s_answer.id.to_s
end
end
it "doesn't mess with other people's answers" do
old_value = another_participant_s_answer.value
put :update, evil_params
expect(another_participant_s_answer.reload.value).to eq(old_value) # pass
expect(response.status).to eq(401) # pass
end
In conclusion, adding the id to the permitted params as stated above is correct and safe.
Fascinating Rails.

Nested resources with independent views model name error in Ruby on Rails

In routes:
resources :users do
resources :service_exps
end
user model:
has_many :service_exps
service_exps model:
belongs_to :user
In service_exps controller new action:
def new
user = User.find(params[:user_id])
#service_exp = user.service_exps.build
render :layout => false
end
In service_exps form:
= form_for ([#service_exp.user, #service_exp]), :remote => true do |s|
.modal-body
.row
.span
= s.label :org_name
= s.text_field :org_name, :class => "span3"
.row
.span
= s.label :position
= s.text_field :position, :class => "span3"
.actions
= s.submit 'Save',:class => "btn btn-info"
It give error
undefined method `user' for nil:NilClass
Please give any suggestion to solve this. Thanks!
These type of errors are pretty easy to debug. The error msg says everything. You are calling a method (in this case 'user') on a nil object.
Follow these steps:
Find the file and line no from the error msg.
Find the object which is nil.
Confirm that the object is initialized properly
Firstly:#service_exp = user.service_exps.build will assign an array of objects to #service_exp - due to a user who has_many :service_exps.
In your view, you doing #service_exp.user - calling user on an array of objects which wont work. Something like #service_exp.first.user would though, when calling user on a specific object in the array. Or remove the .user from form_for

RecordNotFound with accepts_nested_attributes_for and belongs_to

I get
ActiveRecord::RecordNotFound: Couldn't find Client with ID=3 for Order with ID=
when trying to submit an Order form for an existing client. This happens through the form or the console by typing:
Order.new(:client_attributes => { :id => 3 })
payment_form.html.erb:
<%= semantic_form_for #order, :url => checkout_purchase_url(:secure => true) do |f| %>
<%= f.inputs "Personal Information" do %>
<%= f.semantic_fields_for :client do |ff| %>
<%= ff.input :first_name %>
<%= ff.input :last_name %>
<!-- looks like semantic_fields_for auto-inserts a hidden field for client ID -->
<% end %>
<% end %>
<% end %>
Order.rb:
class Order < ActiveRecord::Base
belongs_to :client
accepts_nested_attributes_for :client, :reject_if => :check_client
def check_client(client_attr)
if _client = Client.find(client_attr['id'])
self.client = _client
return true
else
return false
end
end
end
The reject_if idea came from here but I logged the method and it's not even being called! It doesn't matter what its name is!
Note: Feb 2020
Since I'm starting to get downvotes on this 8 years later, adding this note. While this was the original solution I went with 8 years ago, a better one has been proposed by MatayoshiMariano (5 years after my OP).
My Original Fix
Fixed the issue by overloading the client_attributes= method, as described here:
def client_attributes=(client_attrs)
self.client = Client.find_or_initialize_by_id(client_attrs.delete(:id))
self.client.attributes = client_attrs
end
If you only want a new Order with an existing client, without modifying the client, you need to assign the id.
Order.new(client_id: 3)
This is another way to do this without overloading the client_attributes= method and cleanest
The new Order now has the client with ID 3
If you also want to update ant client's attributes you must add the client_attributes, for example:
Order.new(client_id: 3, client_attributes: { id: 3, last_order_at: Time.current })
See https://github.com/rails/rails/issues/7256 from 2012.
If you have has_many relationship, this will work. Tested on Rails 6.0.2
def clients_attributes =(attributes)
# Get IDs for any clients that already exist.
client_ids = attributes.values.map { |a| a[:id] }.compact
# Now find them all and move them to this section.
clients << Client.find(client_ids)
# Update them with standard `accepts_nested_attributes_for` behaviour.
super attributes
end
Had the same error creating a new Thing for existing model with has_many and belongs_to relations.
Fixed it by adding a hidden field for the id of the existing model, for instance User, to the form.
= form.input :user_id, as: :hidden
Then new Thing was created without the error.

Resources