Rails Nested Form Not Saving Nested Attributes - ruby-on-rails

I have the following User controller:
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
#customer = Customer.new
if #user.save
flash.notice = "User '#{#user.email}' was succefully created."
redirect_to user_path(#user)
else
render 'new'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, customer_attributes: [:id, :company])
end
end
And I have the following User model:
class User < ActiveRecord::Base
has_one :customer
accepts_nested_attributes_for :customer, :allow_destroy => true
end
And the following Customer model:
class Customer < ActiveRecord::Base
belongs_to :user
end
Finally, here is the form:
<%= 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 |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :email %><br>
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %><br>
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br>
<%= f.password_field :password_confirmation %>
</div>
<%= f.fields_for :customers do |company| %>
<div class="field">
<%= company.label :company %><br>
<%= company.text_field :company %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
When I submit the form, I see: `Unpermitted parameters: customers' in the log but it appears that I m indeed permitting it.
Also, I want to show the company name for each user in the show and index views. I'm not sure how to do that.
I remember using the build method in the past to get something similar to work but I can't seem to figure it out this time.

Further to #Mandeep's answer, let me give you some further information:
You need to "build" your associated objects for your form
You need to process this as per the association your model has
You need to save the attributes as per said association
The way to do this is relatively simple (outlined by Mandeep). However, the reason why might be a little less obvious:
Build
First, you need to build your associative association. This is vitally important, primarily because Rails (by virtue of being built on Ruby), is an object orientated framework.
Object orientation, without getting into too much detail, means that everything you do with Rails is going to be based around objects. In the case of our beloved Rails, it means that every Model is an object.
By virtue of this fact, the nested model paradigm has to be built in Rails whenever you want to create such a form. To do this, you need to use the build methods - which tell ActiveRecord (Rails' object relational mapper) that you have another associated model / object which you want to populate:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new #-> initializes "User" object
#user.build_customer #-> "builds" the associated object
end
end
This gives Rails a set of associated data which it can populate with your form (considering you call the correct methods)
--
Association
Second, you need to consider the association you have. This is important as singular & multiple associations are handled differently in the "build" process.
You're using a has_one relationship, which means you need to use singular association names (although you can call the associations whatever you want):
If you used a has_many association, you'd need to use the plural association methods:
This explains the need to use the build_customer method; but also should give you the presidence to use the singular association name for all the methods you need to get this working, namely fields_for and params:
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
...
<%= f.fields_for :customer do |c| %>
...
<% end %>
<%= f.submit %>
<% end %>
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.permit(:user).permit(:user, :params, customer_attributes: [:x. :y])
end
end
--
Save
The above controller code will save the attributes you require.
You must understand that passing nested attributes means that the model you're sending the associative data to needs to be subordinated to your "main" model. This happens with the ActiveRecord associations in your models, as discussed initially.
Hopefully this gives you some more clarity

Change your code to this:
def new
#user = User.new
#user.build_customer
end
your form:
<%= form_for #user do |f| %>
// user fields
<%= f.fields_for :customer do |customer| %>
// customer fields
<% end %>
<% end %>
Also there is not need of #customer = Customer.new in your create method.

Related

How to validate multiple models in a single transaction?

In my rails (4.1.6) app, I have a contact model that has_one :address, :email
I construct a contact and related address and email in a single form using fields_for:
views/contacts/new.html.erb
<%= form_for #contact, ... %>
...
<%= fields_for :address do |address_fields| %>
<%= address_fields.text_field :street, ... %>
<%= address_fields.text_field :city, ... %>
...
<% end %>
<%= fields_for :email do |email_fields| %>
<%= email_fields.text_field :display_name, ... %>
<%= email_fields.text_field :mail_id, ... %>
<% end %>
...
<% end %>
I want email to be required, while address is optional. In other words, if email is not provided, none of the 3 models should be created, but if only email is provided, the email and contact must be created.
One way that does work is to validate the params manually in the contacts_controller#create before constructing anything, and flash[:error] and return without saving if email is not specified, or save it if all is well:
contacts_controller.rb
def create
#contact = Contact.new
if(params_email_valid? params)
#contact.save!
#email = Email.create(...)
#email.save!
...
else
flash[:error] = 'Email must be specified to save a contact'
redirect_to :root
end
end
private:
def params_email_valid? params
!(params[:email][:display_name].blank? || params[:email][:mail_id].blank?)
end
Another way that may work is to drop down to SQL and validate everything through direct SQL calls in a transaction.
However, both of these are not 'the rails way', since validations belong in the models. So, I am trying to use some combination of validates_presence_of, validates_associated and custom validators to validate this scenario. The problem here is that model level validation of associated models requires either self to be already saved in the database, or the associated model to be already saved in the database. Is there a way to validate all these models in a single transaction?
Considering you have appropriate validations in the models:
class Contact <
has_many :addresses
has_many :emails
#add
accepts_nested_attributes_for :addresses, :emails #you can add some validations here to like reject_all if blank? see the docs
end
class Address <
belongs_to :contact
end
class Email <
belongs_to :contact
end
In your CompaniesController
def new
#contact = Contact.new
#contact.addresses.new
#contact.emails.new
end
def create
#contact = Contact.new(contact_params)
if #contact.save
#redirect add flash
else
#add flash
#render action: new
end
protected
def contact_params
#permit(#contact_fields, address_attributes: [#address_fields], email_attributes: [#email_fields])
end
And you would like to modify your form like this
<%= form_for #contact, ... do|f| %>
...
<%= f.fields_for :address do |address_fields| %>
<%= address_fields.text_field :street, ... %>
<%= address_fields.text_field :city, ... %>
...
<% end %>
<%= f.fields_for :email do |email_fields| %>
<%= email_fields.text_field :display_name, ... %>
<%= email_fields.text_field :mail_id, ... %>
<% end %>
...
<% end %>
So accepts_nested_attributes helps you validate the child as well as the parent and adds [child]_attributes getters and setters, So normally in your form what was contact[email][display_name] will become contact[email_attributes][display_name]

Defining an object in another model in Rails

I have migrated the :bank_name and :bank_account objects in User model.
I want two objects can be define from the Listings model in the listings/view to the User model columns.
I have already done (belongs_to, has_many)relations between two models.
But when I filled the bank_name and bank_account text_fields in Listing/view, I get the following error:
undefined method `bank_name' for #Listing:400123298
Here is my listing/view code:
<%= form_for(#listing, :html => { :multipart => true }) do |f| %>
...
<div class="form-group">
<%= f.label :bank_name %><br>
<%= f.text_field :bank_name, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :bank_account %><br>
<%= f.text_field :bank_account, class: "form-control" %>
</div>
</end>
listing/controller:
def new
#listing = Listing.new
end
def create
#listing = Listing.new(listing_params)
#listing.user_id = current_user.id
#listing.user_id = User.bank_name.build(params[:bank_name])
#listing.user_id = User.bank_account.build(params[:bank_account])
end
Several issues for you
Nested
As mentioned in the comments, what you're looking at is a nested model structure.
Simply, this means you'll be able to create an associative model from your "parent" - giving you the ability to define the attributes you need in your "parent" model, passing them through to the nested. This functionality is handled by accepts_nested_attributes_for in your parent model
The best resource you can use is this Railscast (only the start):
--
Fix
Here's how you can fix the problem:
#app/models/listing.rb
class Listing < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :bank_account
accepts_nested_attributes_for :bank_account
end
#app/models/bank_account.rb
class BankAccount < ActiveRecord::Base
belongs_to :user
end
#app/controllers/listings_controller.rb
class ListingsController < ApplicationController
def new
#listing = current_user.listings.new
#listing.user.build_bank_account
end
def create
#listing = Listing.new listing_params
#listing.save
end
private
def listing_params
params.require(:listing).permit(:listing, :params, user_attributes: [ bank_account_attributes: [] ])
end
end
This will help you do the following:
#app/views/listings/new.html.erb
<%= form_for #listing do |f| %>
...
<%= f.fields_for :user do |u| %>
<%= u.fields_for :bank_account do |b| %>
<%= b.text_field :name %>
<%= b.text_field :number %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
There is a slight twist to this tail, in that I'm not sure whether your passing of attributes through to your User model. This would be okay if the user was being created at the same time as your other attributes, but as it isn't, we may need to refactor the process of passing the nested data through
If this does not work, please comment & we can work to fix it!

Virtus: Replace accepts_nested_attributes (one-to-many) with a form object

Since more than a month I try to get behind the secrets of form objects in Rails 4.
Using virtus, I am already able to build very simple forms. However, I fail to develop a form object that replaces accepts_nested_attributes_for (in the model) and fields_for (in the form view).
In this question I explain a small phonebook-example: the form provides the possibility to enter a person's name and 3 phone numbers at once (find the whole code here).
Now I try to do the same with a form object. I get as far as this:
# forms/person_form_new.rb
class PersonFormNew
class PhoneFormNew
include Virtus
include ActiveModel::Model
attr_reader :phone
attribute :phone_number, String
end
include Virtus
include ActiveModel::Model
attr_reader :person
attribute :person_name, String
attribute :phone, PhoneFormNew
def persisted?
false
end
def save
if valid?
persist
true
else
false
end
end
private
def persist
#person = Person.create(name: person_name)
#person.phones.build(:phone)
end
end
# views/people/new.html.erb
<h1>New Person</h1>
<%= form_for #person_form, url: people_path do |f| %>
<p>
<%= f.label :person_name %> </ br>
<%= f.text_field :person_name %>
</p>
<p>
<%= f.fields_for :phone do |f_pho| %>
<%= f_pho.label :phone_number %> </ br>
<%= f_pho.text_field :phone_number %>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
This gives me the error
undefined method `stringify_keys' for :phone:Symbol
line: #person.phones.build(:phone)
I fear however, this is not the only error.
Can you point me the way to realize a one-to-many assignment with a form object (preferable using Virtus)?
One solution is to create the associated object in a separate function on the form model. I was succussful by doing the following:
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
You can find an extended explanation here: http://w3facility.org/question/how-to-create-another-object-when-creating-a-devise-user-from-their-registration-form-in-rails/

Ruby on rails: Trouble creating an object with multiple associations

I am having a problem with creating an object with an association.
I have a Message model that belongs_to a job, and a user or runner. Inside my jobs/index.html I want to show a list of jobs with their corresponding messages and a form to create a new message for that particular job.
The problem is whenever I create a message, job_id stays nil. I am new to ruby on rails, so I still dont fully understand this stuff.
Here is part of my jobs/index.html (NOTE: not my actual code, I am in class so I just typed this up, may contain syntax errors).
<% #jobs.each do |job| %>
<p> <%= job.body %> </p>
<%= form_for job.messages do |f| %>
<%= f.label :body %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
<%if job.messages.present? %>
<ul>
<% job.messages.each do |message| %>
<li>
<p> message.description <p>
</li>
<% end %>
</ul>
<% else %>
<p> No messages <p>
<% end %>
<% end %>
Here is the create method in message controller (NOTE: current_login can be a runner or user, they both share the same attributes)
def create
#message = current_login.messages.new(params[:message])
#message.save
end
Job controller index action
def index
#jobs = Job.all
end
Message model
class Message < ActiveRecord::Base
attr_accessible :description
belongs_to :user
belongs_to :runner
belongs_to :job
end
User model
class User < ActiveRecord::Base
attr_accessible :username
has_many :jobs
end
Runner model
class Runner < ActiveRecord::Base
attr_accessible :username
has_many :jobs
end
Job model
class Job < ActiveRecord::Base
attr_accessible :body
has_many :messages
belongs_to :user
belongs_to :runner
end
Whenever I submit the message form inside the jobs/index.html view, it seems to successfully create a message with user_id or runner_id successfully filled out (depending on who posted the message), but I am getting nil for the job_id attribute.
Since your message belongs to job, i think you should be creating the nested resources within the jobs form.
Your new controller function inside the jobs model should build the association like so:
def new
#job = Job.new(params[:job])
#message = #job.build_message
end
your create model just needs to save the parent model:
def create
#job = Job.create(params[:job])
end
For lots of detailed information on how to do this, watch this railscast: http://railscasts.com/episodes/196-nested-model-form-part-1
I should also add, if you are simply trying to add a message to an existing job, just pass the parameter for the job_id correctly in your form, AND make sure the job you're referencing actually exists.
To solve this problem, I decided to manually create the tie between the message and the job it belongs to through a hidden field in the form.
<%= form_for(#message) do |f| %>
<%= f.label :body, "Description" %>
<%= f.text_area :body %>
<%= f.hidden_field :job_id, value: job.id %>
<%= f.submit 'Create message', class: 'button small secondary' %>
<% end %>

How to get Rails build and fields_for to create only a new record and not include existing?

I am using build, fields_for, and accepts_nested_attributes_for to create a new registration note on the same form as a new registration (has many registration notes). Great.
Problem: On the edit form for the existing registration, I want another new registration note to be created, but I don't want to see a field for each of the existing registration notes.
I have this
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
end
and this
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
and in the view I am doing this:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
and it is creating a blank text area for a new registration note (good) and each existing registration note for that registration (no thank you).
Is there a way to only create a new note for that registration and leave the existing ones alone?
EDIT: My previous answer (see below) was bugging me because it's not very nice (it still loops through all the other registration_notes needlessly). After reading the API a bit more, the best way to get the behaviour the OP wanted is to replace:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for :registration_notes, #registration.registration_notes.build do |n| %>
fields_for optionally takes a second parameter which is the specific object to pass to the builder (see the API), which is built inline. It's probably actually better to create and pass the new note in the controller instead of in the form though (just to move the logic out of the view).
Original answer (I was so close):
Just to clarify, you want your edit form to include a new nested registration note (and ignore any other existing ones)? I haven't tested this, but you should be able to do so by replacing:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for #registration.registration_notes.build do |n| %>
EDIT: Okay, from a quick test of my own that doesn't work, but instead you can do:
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content if n.object.id.nil? %>
<% end %>
This will only add the text area if the id of the registration note is nil (ie. it hasn't been saved yet).
Also, I actually tested this first and it does work ;)
If you want to create a new registration form on your edit action, you can just instantiate a new registration_note object. Right now, your form is for the existing registration object.
I believe this is what you want:
class RegistrationsController < ApplicationController
def edit
#new_registration_note = RegistrationNote.new
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
In your view, you should pass a hidden param that references the registration record id:
<%= form_for #new_registration_note do |r| %>
<%= r.hidden_field :registration_id, :value => #registration.id %>
<%= r.text_area :content %>
<% end %>
Now, you can create your new registration note that belongs to #registration. Make sure you have a column in your registration_notes table to point to the registration. You can read more about associations here: http://guides.rubyonrails.org/association_basics.html
Thank you so much for your help as I said in my post the only problem with the approach from "Zaid Crouch"(I don't know how to make a reference to a user hehe) is that if the form has error fields the form will be clear and boom after the page reloading you'll have nothing filled in your form and can you imagine if you form is like 20 or 30 fields that would be a terrible user experience of course
Here is my solution that works with validation models:
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
has_one :new_registration, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration
end
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.build_new_registration
end
end
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
I'm using simple_form in my example if you want to see the same working with validations and transaction take a look at the complete post here:
http://elh.mx/ruby/using-simple_form-for-nested-attributes-models-in-a-has_many-relation-for-only-new-records/
As Heriberto Perez correctly pointed out the solution in the most upvoted answer will simply discard everything if there's a validation error on one of the fields.
My approach is similar to Heriberto's but nevertheless a bit different:
Model:
class Registration < ActiveRecord::Base
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
# Because 0 is never 1 this association will never return any records.
# Above all this association don't return any existing persisted records.
has_many :new_registration_notes, -> { where('0 = 1') }
, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration_notes
end
Controller:
class RegistrationsController < ApplicationController
before_action :set_registration
def edit
#registration.new_registration_notes.build
end
private
def set_registration
#registration = Registration.find(params[:id])
end
def new_registration_params
params.require(:registration).permit(new_registrations_attributes: [:content])
end
end
View:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>

Resources