Nested Rails 4 Form Accessing Array - ruby-on-rails

I have a nested form that has 3 fields repeated 7 times. I want to do a check that if the first field group is empty, then it will stop the process and return an error asking a user to fill in those fields.
I already have a check in that it will drop any empty field groups from the Create action in the controller, that is the "reject_if: => :all_blank" part of the model. However, it deletes the first entry, which I would instead rather run a check on. This code doesn't work, and I don't know where to go from here besides trying to refine the check_attendee_form method. Any help would be appreciated out there.
Here are the related Models:
class Registration < ActiveRecord::Base
# Database Relationships
belongs_to :event
belongs_to :user
has_many :attendees
# Form relationships
accepts_nested_attributes_for :attendees,
:allow_destroy => true,
:reject_if => :all_blank
class Attendee < ActiveRecord::Base
# Database relationships
belongs_to :event
belongs_to :user
delegate :event, :event_id, to: :registration
# Validations
validates :first_name, :last_name, presence: true
Controller Create and New Actions and Methods
def new
#registration = #event.registrations.new
7.times { #registration.attendees.build }
end
def create
#registration = #event.registrations.new(registration_params)
#registration.user = current_user
check_attendee_form
respond_to do |format|
if #registration.save
format.html { redirect_to event_path(#event), notice: 'You are now registered.' }
format.json { render :show, status: :created, location: #registration }
else
format.html { render :new }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
end
def check_registration
#check = Registration.find_by event_id: #event, user_id: current_user
if #check.nil?
#registration = #event.registrations.new
#registration.user = current_user
else
redirect_to event_path(#event),
notice: "You're already registered!"
end
end
def check_attendee_form
#attendees_check = #registration.find_by(attendees_attributes: params[:first_name])
if #attendees_check.first.nil?
render :new, notice: "You need to put in your name at least!"
else
end
end
And finally, the essential form info:
<%= simple_form_for [#event, #registration], :html => { :class => 'form-horizontal' } do |f| %>
<%= render 'shared/errors', object: #registration %>
<%= f.simple_fields_for :attendees, defaults: { input_html: { class: 'form-horizontal' } } do |a| %>
<div>
<%= a.label :first_name %>:
<%= a.text_field :first_name %>
<%= error_span([:first_name]) %>
<%= a.label :last_name %>:
<%= a.text_field :last_name %>
<%= error_span([:last_name]) %>
<%= a.label :fundraising_goal %>:
<%= a.number_field :fundraising_goal, placeholder: 'No Commas' %>
<%= error_span([:fundraising_goal]) %>
Here are the params that are getting submitted:
{"utf8"=>"✓",
"authenticity_token"=>"dNR5QCBFplsAG0wzy87+hzaKuG6h2Mlb6xpmKEM0Kko=",
"registration"=>{"attendees_attributes"=>{"0"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"1"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"2"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"3"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"4"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"5"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""},
"6"=>{"first_name"=>"",
"last_name"=>"",
"fundraising_goal"=>""}}},
"commit"=>"Create Registration",
"event_id"=>"5"}
How do I access the first array object with attendees_attributes of ID [0] in this form submission?

If you want to ensure that at least one Attendee is entered for a registration, I believe you could drop the check_attendee_form method and add the following validation to registration.rb:
validate :check_minimum_attendees
def check_minimum_attendees
errors.add(:attendees, 'must be entered') if attendees.size == 0
end

Your form attributes is nested hash, you can access attendees using id like 0, 1 etc
i.e params[registration][attendees][0]

If I am correct, the problem is with :reject_if condition.
You can use a Proc (or lambda) to 'reject' any unsuitable nested attributes.
:reject_if => lambda { |e| e[:last_name].blank? && (e[:_destroy].blank? || e[:_destroy] == 'false') }
Use validation as #keyzee suggested:
validate :check_attendees
def check_attendees
errors.add(:attendees, 'must be entered') unless attendees.count
attendees.each do |a|
errors.add(:attendees, 'error message here') if condition_here
end
end
Or create custom validator in lib folder (lib/check_attendees_validator.rb):
class CheckAttendeesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << 'error_message' if condition_here
end
end
And then use it in a model:
validates :attendees, check_attendees: true

Related

Forms for has_one but not always

I have two models: Personand User. A person can have a single user or no user at all, but every user have to be belong to a person.
I have set both models like so:
Person (has a has_user attribute):
has_one :user, dependent: :destroy
accepts_nested_attributes_for :user
User (has person_id attribute):
belongs_to :person
validates_presence_of :login, :password
validates_uniqueness_of :login
In the Person form I have a nested form_for that only shows up if I check a check box that also set the has_user attribute to true on the Person's form part.
My issue is that whenever I submit a person that has no user, it still tries to validate the user. How can I make it work?
UPDATE:
In my view:
<div class="form-group">
<%= person_form.label :has_user, :class => 'inline-checkbox' do %>
Possui usuário
<%= person_form.check_box :has_user, {id: "hasUser", checked: #person.has_user} %>
<% end %>
</div>
</div>
<div id="userPart" class="findMe"
<% if #person.user.id.blank? %> style="display:none;"
<% end %>>
<h2> User: </h2>
<div class="container">
<%= person_form.fields_for :user do |user_form| %>
<%= render partial: 'users/campos_user', locals: {form: user_form} %>
<% end %>
</div>
</div>
In my Person.js:
jQuery ->
$(document).ready ->
$(document).on 'turbolinks:load', #Added line
console.log("Turbolinks ready.")
$("#hasUser").change ->
$("#userPart").toggle();
return
return
return
return
In my Person Controller:
def new
#person= Contato.new
#person.ativo = true
#page_title = 'Novo person'
#person.build_user
end
def create
#person = Person.new(person_params)
respond_to do |format|
if #person.save
flash[:notice] = 'Contato foi criado com sucesso.'
if #person.has_user == true
format.html {redirect_to controller: :users, action: :new, person_id: #person.id}
else
format.html {redirect_to #person}
end
format.json {render :show, status: :created, location: #person}
else
flash[:warn] = "Erro ao criar contato."
format.html {render :new}
format.json {render json: #person.errors, status: :unprocessable_entity}
end
end
end
Person's params:
def person_params
params.require(:person).permit(:id, :company_id,
:active, :name,
:cargo, :celular,
:email, :nascimento,
:observacoes, :mensagem_instantanea,
:tipo_msg_inst, :possui_usuario,
user_attributes: [:login, :password, :permits, :id, :person_id, :_destroy])
end
You can reject submitting user attributes by using reject_if option
accepts_nested_attributes_for :user, :reject_if => :no_user_connected
def no_user_connected(attributes)
attributes[:user_attributes][:login].blank?
end
This should discard the user part of the form (all user attributes). Please note that I dunno how your form or controller looks like, so you might need to build the condition differently.
You can also make the validations on user conditional. But overall the best way to avoid convoluted validation problems is to use some kind of form objects where you encapsulate the validations process. https://thoughtbot.com/blog/activemodel-form-objects

Nested form attributes wont save

I am required to use nested forms on an assignment I am working on and I got stuck because my nested form attributes wont submit to database.
Here is what my controller looks like
def new
#booking = Booking.new
params[:no_of_passengers].to_i.times { #booking.passengers.build }
end
def create
#booking = Booking.new(booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to '/booking_confirmed', notice: 'Booking was successfully created.' }
format.json { render :show, status: :created, location: #booking }
else
format.html { render :new }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
private
def booking_params
params.permit(
:airline, :origin, :destination, :departure_date, :departure_time, :arrival_date,
:arrival_time, :flight_id, :price, :no_of_passengers, :user_id, :booking,
passenger_attributes: [
:id,:booking_id, :name, :email,:done,:_destroy
]
)
end
Here is the association between the models
class Booking < ActiveRecord::Base
has_many :passengers
accepts_nested_attributes_for :passengers, reject_if: lambda { |attributes| attributes['name'].blank? }
end
class Passenger < ActiveRecord::Base
belongs_to :bookings
end
And here is the form
<%= form_for #booking do |b| %>
<%= b.fields_for :passengers do |p| %>
<%= p.text_field :name, placeholder: "Passenger Name" %>
<%= p.text_field :email, placeholder: "Passenger Email" %>
<% end %>
<% end %>
I checked the passenger table using Passenger.all in rails console and it returns nothing.
What am I doing wrong?
After a pairing session with sunnyk, I was able to see the errors.
The first error was that my class Passenger has belongs_to :bookings instead of belongs_to :booking. This is a common error though. The Associations between these classes now looks like:
class Booking < ActiveRecord::Base
belongs_to :flight
has_many :passengers
accepts_nested_attributes_for :passengers, reject_if:
lambda {|attributes| attributes['name'].blank?}, :allow_destroy => true
end
class Passenger < ActiveRecord::Base
belongs_to :booking
end
class Flight < ActiveRecord::Base
has_many :bookings
has_many :passengers, through: :bookings
accepts_nested_attributes_for :passengers
accepts_nested_attributes_for :bookings
end
Next:
Instead of using the default value of no_of_passengers for building my nested form, I used the cocoon gem, which makes nested forms building and management easier. I also crated a new params method, in which I made the flight_id permitted, and then passed it as an argument for my booking instance in my new method alongside my current user. So now my new method looks like this.
def new
#booking = Booking.new(new_booking_params)
#booking.user = current_user if current_user
end
def new_booking_params
params.permit(:flight_id)
end
After that, I had to make another params method for my create method, so as to allow the parameters I want in the bookings table, this include the passengers_attributes. Now my create method looks like this.
def create
#booking = Booking.new(another_booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to '/booking_confirmed', notice: 'Booking was successfully created.' }
format.json { render :show, status: :created, location: #booking }
else
format.html { render :new }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
def another_booking_params
params.require(:booking).permit(:flight_id, :user_id, :no_of_passengers,
passengers_attributes:[:name, :email])
end
Lastly, I had to adjust my form to look like this.
<%= form_for(#booking, url: bookings_path) do |f| %>
<%= f.hidden_field(:flight_id)%>
<%= f.hidden_field(:user_id) %>
<%= f.hidden_field(:no_of_passengers)%>
<%= f.fields_for :passengers do |passenger| %>
<%= render 'passenger_fields', :f => passenger %>
<% end %>
<%= link_to_add_association 'Add Another passenger',f, :passengers, :class => 'btn btn-primary add' %>
<%= submit_tag "Book Now", class: "btn btn-primary book" %>
<% end %>
and passenger_fields partial looks like.
<div class="nested-fields form-inline">
<div class="form-group">
<%= f.text_field :name, :class => "form-control", placeholder: "Passenger Name" %>
</div>
<div class="form-group">
<label>-</label>
<%= f.text_field :email, :class => "form-control", placeholder: "Passenger Email" %>
</div>
<div class="links pull-right">
<%= link_to_remove_association "Delete", f, class: "btn btn-danger" %>
</div>
<hr>
</div>
All that did the trick. I hope this will help others to understand nested forms better

fields_for not creating a record when making a new parent record

I am trying to automatically create a child record (Participant) when I create a parent (Project) record. I can create the parent (Project) fine and, on other forms, I can create the child (Participant). I cannot seem to create the child (Participant) at the same time as the parent.
I am on Rails 4, and so I've set my strong params carefully. I just don't understand what I'm doing wrong.
Parent Controller:
class ProjectsController < ApplicationController
def new_project
#title = params[:ti]
#project = Project.new
#project.participants.build
end
def create_project
#project = Project.new(project_params)
#template = Template.find(params[:t])
#project.participants.build
#title = params[:ti]
respond_to do |format|
if #project.save
#project.participants.save
format.html { redirect_to new_milestones_path(:p => #project.id), notice: 'Great! We saved your project details.' }
else
format.html { redirect_to new_project_path(t: #template.id, ti: #title)
}
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def project_params
params.require(:project).permit( :id, :title, :starts, participants_attributes: [:id, :email, :title, :status, :project_id])
end
end
Models:
class Participant < ActiveRecord::Base
belongs_to :project, inverse_of: :participants
........
end
class Project < ActiveRecord::Base
has_many :participants, dependent: :destroy, inverse_of: :project
accepts_nested_attributes_for :participants, allow_destroy: true, reject_if: proc { |a| a["email"].blank? }
.........
end
Form:
<%= form_for #project, url: create_project_path(ti: #title), html: { :multipart => true, :class=> "form-horizontal", id: "basicForm" }do |f| %>
<%= f.fields_for :participants do |ff|%>
<%= ff.hidden_field :email, :value => current_user.email %>
<%= ff.hidden_field :title, :value => 'Organizer' %>
<%= ff.hidden_field :status, :value => 'accepted' %>
<% end %>
<%= f.text_field :title, :placeholder => 'Your Project Title'%>
<%= f.text_field :starts, :placeholder => 'mm/dd/yyyy'%>
<%= f.submit ' SAVE PROJECT' %>
<% end %>
UPDATE:
I added #project.participants.build as Samo suggested (and I've updated my code above), which makes the fields_for visible...but my project doesn't save...it redirects back to new_project_path.
I believe I see the issue. In your new_project action, try this:
def new_project
#title = params[:ti]
#project = Project.new
#project.participants.build
end
To elaborate: fields_for is not going to render anything if the association is blank/empty. You need to have at least one participant returned by #project.participants in order to see its fields. #project.participants.build will simply insert a new instance of the Participant class into the association.
As you're using Rails 4 in your application, you don't need to call accepts_nested_attributes_for, because you are already calling params.requirein your Controller.
After #participant = Participant.new you didn't call Participant.saveaction. You do call a #project.save inside your if condition and you should do that for your #participant too. You can call #project.save before redirecting to project_path. I'm not sure if that is a correct approach, but you can try if it works. :-)

Nested form not working to save client_id and user_id

I have Users, Users have many Clients and Contacts. Clients also have many Contacts (a Contact belongs to both Users and Clients).
In my client view, I want to create a new Client and on the same form allow the user to create the first Contact for that client. Initially, I thought using nested attributes would do the trick but I'm running into some issues. When I go to save #client in clients_controller#create, I can't save because user_id can't be blank and client_id can't be blank for the Contact. Here's what I have so far:
clients controller index (where the new client form is located):
def index
#clients = current_user.clients
#client = Client.new
#contact = #client.contacts.build
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clients }
end
end
and the create method:
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
and the form:
= form_for(#client) do |f|
= f.fields_for(:contacts) do |contact|
but when I go to save it requires a client_id and user_id...but I can't really set those using the nested attributes. how can I accomplish this? is there a better way of doing this? here's my params:
{"name"=>"asdf", "contacts_attributes"=>{"0"=>{"name"=>"asdf", "email"=>"asdf#gmail.com"}}}
I just tried adding the missing values directly into the contacts_attributes but since #client hasn't been saved yet, I can't assign the client.id to contact:
params[:client][:contacts_attributes]["0"].merge!(:user_id => current_user.id)
params[:client][:contacts_attributes]["0"].merge!(:client_id => #client.id)
even when user_id is set...it still says user is missing.
Did you add accepts_nested_attributes_for to your model? You need something like this:
class Client < ActiveRecord::Base
has_many :contacts
accepts_nested_attributes_for :contacts
end
Also, you should be using build in your create action:
#client = current_user.clients.build(params[:client])
Here's my setup that worked for your example:
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :clients_attributes
has_many :clients
accepts_nested_attributes_for :clients
end
app/models/client.rb:
class Client < ActiveRecord::Base
attr_accessible :company, :contacts_attributes
belongs_to :user
has_many :contacts
accepts_nested_attributes_for :contacts
end
app/models/contact.rb:
class Contact < ActiveRecord::Base
attr_accessible :email
belongs_to :client
end
app/controllers/users_controller.rb:
class UsersController < ApplicationController
# ...
def new
#user = User.new
#client = #user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The actual form:
<% # app/views/users/new.erb %>
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.fields_for :clients do |client_form| %>
<h5>Client</h5>
<%= client_form.label :company, "Company name" %>
<%= client_form.text_field :company %>
<div class="field">
<h5>Contact</h5>
<%= client_form.fields_for :contacts do |contact_form| %>
<%= contact_form.label :email %>
<%= contact_form.text_field :email %>
<% end %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The form looks like this:
Here's how params sent by forms look like:
{
"utf8"=>"✓",
"authenticity_token" => "bG6Lv62ekvK7OS86Hg/RMQe9S0sUw0iB4PCiYnsnsE8=",
"user" => {
"name" => "My new user",
"clients_attributes" => {
"0" => {
"company" => "Client's Company Name LLC",
"contacts_attributes" => {
"0" => {
"email" => "emailbox#client.com"
}
}
}
}
},
"commit" => "Create User"
}
Update
To enable adding more companies/contacts afterwards, change the UsersController#edit action to this:
class UsersController < ApplicationController
# ...
def edit
#client = current_user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The following validations may be causing this problem since the parent id has not assigned to a child object at the time the validations are run for it. The workaround to this is to either remove these validations or set them to run on update only. This way you lose out on these validations for the create method but it works.
# client.rb
validates :user, presence: true
# contact.rb
validates :client, presence: true

Rails: Updating Nested attributes - undefined method `to_sym' for nil:NilClass

Edit: Added the update action, and on what line the error occurs
Model:
class Match < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :match_teams
has_many :teams, :through => :match_teams
accepts_nested_attributes_for :match_teams, :allow_destroy => true
end
Controller:
def new
#match = Match.new
#match_teams = 2.times do
#match.match_teams.build
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #match }
end
end
def update
#match = Match.find(params[:id])
respond_to do |format|
if #match.update_attributes(params[:match])
format.html { redirect_to #match, notice: 'Match was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
Nested model:
class MatchTeam < ActiveRecord::Base
belongs_to :match
belongs_to :team
end
Association:
class Team < ActiveRecord::Base
has_and_belongs_to_many :matches
end
View:
<%= form_for(#match) do |f| %>
<%= f.fields_for :match_teams, #match_teams do |builder| %>
<%= builder.collection_select :team_id, Team.all, :id, :name, :include_blank => true %>
<% end %>
<% unless #match.new_record? %>
<div class="field">
<%= f.label :winning_team_id %><br />
<%= f.collection_select :winning_team_id, #match.teams, :id, :representation %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Params:
Processing by MatchesController#update as HTML
Parameters: {"utf8"=>"Ô£ô", "authenticity_token"=>"QIJChzkYOPZ1hxbzTZS8H3AXc7i
BzkKv3Z5daRmlOsQ=", "match"=>{"match_teams_attributes"=>{"0"=>{"team_id"=>"1", "
id"=>""}, "1"=>{"team_id"=>"3", "id"=>""}}, "winning_team_id"=>"3"}, "commit"=>"
Update Match", "id"=>"2"}
Creating a new match with 2 teams work fine, the edit view also shows the correct values, but the update action gives me this error.
undefined method `to_sym' for nil:NilClass
app/controllers/matches_controller.rb:65:in `block in update'
line 65: if #match.update_attributes(params[:match])
I've figured it out. I read that a join table like MatchTeams doesn't need an ID. I'm guessing this is true when not doing any nested forms. I redid my migration removing the exclusion of the id column, and now everything works fine. Don't we all love this stupid errors? :)
Without seeing the offending to_sym in your code, just know that the thing it's attached to has not been defined properly. If this is a variable such as #var.to_sym, you most likely:
Haven't set #var at all
Set it but it's returning nil because there are no matches (e.g. #var = #project.companies.first but #project has no companies tied to it).
You are missing a relevant bit of data in your params. If your to_sym is relying on data submitted through the form, it won't work if the user leaves out the bit of data you're assuming. In this case, you should test first to see if the data was entered before running .to_sym on it.

Resources