confusion about accepts_nested_attributes_for - ruby-on-rails

Somehow, I was under impression that accepts_nested_attributes_for will let me populate child object through parent:
person.update_attributes person_hash
but in practice I ended up doing this:
person.address.update_attributes person_hash[:address]
person_hash.delete :address
person.update_attributes person_hash
Now, http://guides.rubyonrails.org mentions accepts_nested_attributes_for only indirectly and API documentation for this method is also quite cryptic.
1) Could someone show basic use-case for accepts_nested_attributes_for? I mean, I understand how it's declared, I'm confused about the difference it makes.
2) Is the way I populate nested object the 'right' one in rails, or there's something more 'elegant'?
Thanks!
update
Model, for clarity
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
update2, for j.
Form declaration goes like this
<% fields_for "people", person, :index => person.id do |person_form| %>
...
<% person_form.fields_for person.address do |address_form| %>
<%= address_form.text_field :street %>
<% end %>
...
<% end %>
But it gives me html names like people[66][address][street] and not people[66][address_attributes][street]

With accepts_nested_attributes_for you can do the following:
# example from railsapi.com
# view
<% form_for #person, :url => { :action => "create" } do |person_form| %>
Name: <%= person_form.text_field :name %>
...
<% person_form.fields_for :address do |address_fields| %>
Street : <%= address_fields.text_field :street %>
Zip code: <%= address_fields.text_field :zip_code %>
<% end %>
<% end %>
With this, your parameters will look like
params = {
:person => {
:name => 'Jack',
:address_attributes => { :street => 'Street', :zip_code => '11111' }
}
}
and you're able to create a person and the related address just using
#person = Person.create(params)
For more information, railsapi.com. I hope it helps.
Edit
I have no idea why your params look like this :/
But using
#person.address.update_attributes person_hash.delete(:address) OR
person.address.update_attributes person_hash[:address]
person_hash.delete :address
person.update_attributes person_hash
is not wrong. Just could be done with more elegant code, as you said.

Related

Rails/SimpleForm: How do I specify the params for two sets of nested attributes?

I have a model that has two different associations with another model:
class Order < ApplicationRecord
belongs_to :customer, required: true
belongs_to :delivery_address,
class_name: 'Address',
foreign_key: :delivery_address_id
belongs_to :collection_address,
class_name: 'Address',
foreign_key: :collection_address_id
accepts_nested_attributes_for :collection_address, :delivery_address
end
It accepts nested attributes:
<h1>Edit order</h1>
<%= simple_form_for #order, url: order_path do |form| %>
<strong>Collection address</strong>
<%= form.simple_fields_for #collection_address, as: :collection_address do |collection_address_form| %>
<%= collection_address_form.input :address1 %>
...
<% end %>
<br>
<strong>Re-delivery address</strong>
<%= form.simple_fields_for #redelivery_address, as: :delivery_address do |delivery_address_form| %>
<%= delivery_address_form.input :address1 %>
...
<% end %>
<%= form.button :submit %>
<% end %>
I want to receive two sets of params: params[:collection_address and params[:delivery_address.
However when the form submits, the controller does not receive params for both addresses. Instead it receives one set under the key params[:address].
I understand that form_for accepts the :as option. I've used it above. I expect to gete params[:delivery_address] and params[:collection_address] but I only get params[:address].
Is this supposed to work with Simple Form or do I need to do something different? Is there anything wrong with my code that's preventing the two sets of params from being created?
You can specify the
<%= delivery_address_form.input :address1, input_html: {name:'delivery_address[address1]' } %>
for each of the fields. I know this feels kind of dirty, but eh... It's going to get the job done

Complex Rails form with Nested Attributes (Rails 5)

I have a form, that is saving the main attribute, but not its nested attributes. I have dug into a lot of documents, and seem to be doing things correctly, but still get an error that my nested attributes "must exist".
My interview attributes are saving correctly to the database, but my logs show "Unpermitted parameters: student, parents"
My code is modified for brevity, but I will still try to be thorough enough to get some direction as to what might be going wrong ...
Models (which I include accepts_nested_attributes for :student, :parents)
:student is singular since it has a has_one relationship
:parents is plural since it has a has_many relationship
class Interview < ApplicationRecord
has_one :student
has_many :parents
accepts_nested_attributes_for :student, :parents
end
class Student < ApplicationRecord
belongs_to :interview
end
class Parent < ApplicationRecord
belongs_to :interview
end
Controller
class InterviewsController < ApplicationController
def index
#interviews = Interview.all
end
def show
#interview = Interview.find(params[:id])
end
def new
#interview = Interview.new
#interview.build_student
2.times { #interview.parents.build }
end
def create
#interview = Interview.new(interview_params)
if #interview.save
redirect_to #interview
else
render :action => 'new'
end
end
private
def interview_params
params.require(:interview).permit(:date_today, :date_contact, :purpose_of_call, :problems_start_date, :cause, :violence, :running_away, :police_contact, :suicide, :self_harm, :other_info, :testing, :hospitalization, :medications, :school_problems, :teacher_relationships, :parent_goals, :notes,
student_attributes: [:id, :name, :age, :height, :weight, :dob, :interview_id],
parents_attributes: [:id, :name, :relationship, :parentage, :address, :phone_home, :phone_work, :phone_mobile, :phone_mobile, :email, :employer, :notes, :interview_id] )
end
end
Form (important bits)
<%= form_for(#interview) do |f| %>
<p class="inline">
<%= f.label :date_today, 'Today\'s Date' %>
<%= f.date_select(:date_today, :order => [:month, :day, :year], :start_year => 2000, :end_year => Date.today.year) %>
</p>
<p class="inline float_right">
<%= f.label :date_contact, 'Initial Contact' %>
<%= f.date_select(:date_contact, :order => [:month, :day, :year], :start_year => 2000, :end_year => Date.today.year) %>
</p>
<%= f.fields_for :student do |student_form| %>
<p><%= student_form.text_field :name, placeholder: 'Name' %></p>
<p class="inline">
<%= student_form.label :age %>
<%= student_form.text_field :age %>
</p>
<p class="inline">
<%= student_form.label :height %>
<%= student_form.text_field :height %>
</p>
<p class="inline">
<%= student_form.label :weight %>
<%= student_form.text_field :weight %>
</p>
///// removed for brevity /////
<% end %>
<%= f.fields_for :parents do |parent_form| %>
<%= render 'parents', :f => parent_form %>
<% end %>
Parent Partial
<p>
<%= f.label :name, 'Name' %>
<%= f.text_field :name %>
</p>
//// and more of the same /////
Routes
resources :interviews do
resources :student
resources :parents
end
The website form (at it's current state) can be found here: www.compassconsultingwi.com/interviews/new
and the link to the github can be found here: https://github.com/plantoteachme/compassconsultingwi
Params returns this ..
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nU4WM2RO5GJd36eaSLHMxhRQCOnY8EPjDhUdFBHlYGkcw6H7/Oc5y7kFx0HMU9nm5cc47ZZZBDW6oQ2QNF5yhA==", "interview"=>{"date_today(2i)"=>"11", "date_today(3i)"=>"16", "date_today(1i)"=>"2016", "date_contact(2i)"=>"10", "date_contact(3i)"=>"23", "date_contact(1i)"=>"2016", "student"=>{"name"=>"John", "age"=>"12", "height"=>"5 feet", "weight"=>"123 lbs", "dob(2i)"=>"3", "dob(3i)"=>"13", "dob(1i)"=>"2004", "strengths"=>"Great with his siblings", "weaknesses"=>"Lazy", "likes"=>"Food", "dislikes"=>"Chores", "medical_prolems"=>"ADD", "religous_training"=>"Catholic", "ethnic_issues"=>"none", "grade_level"=>"6"}, "parents"=>{"name"=>"Jamie", "relationship"=>"Mom", "parentage"=>"Strict", "address"=>"Miwaukee Wi", "phone_home"=>"555-1000", "phone_work"=>"555-1001", "phone_mobile"=>"555-1002", "email"=>"jj#jj.com", "employer"=>"Googleer", "notes"=>"PhD in Computer Science"}, "purpose_of_call"=>"Depression causing suicidal tendencies", "problems_start_date"=>"When we moved from Nigeria last year", "cause"=>"Relocating", "violence"=>"none", "running_away"=>"no", "police_contact"=>"no", "suicide"=>"Hasn't acted on it, but talks about it", "self_harm"=>"Minor bruising from \"sports\"", "other_info"=>"", "testing"=>"Yes, for ADD", "hospitalization"=>"no", "medications"=>"Regeline", "school_problems"=>"Getting bullied", "teacher_relationships"=>"Strained", "parent_goals"=>"Improve self awareness", "notes"=>"Our family was in Nigeria for mission work"}, "button"=>""}
Unpermitted parameters: student, parents
Try to use cocoon gem.
You can build a model object using link_to_add_association method of cocoon gem.
Also, you can remove object using link_to_remove_association
A fully working example here: https://github.com/nathanvda/cocoon/wiki/ERB-examples

saving array values in each new row

I have tried for sometime and i think i got it wrong.
The form that i use is a nested form with fields_for and all i wanted is to save each of the array values in the rails select function into new rows in the db.
I have serialized :newpages in my blackwhite.rb model.
<% forms_for #prints do |f| %>
...
...
<%= f.fields_for :blackwhites_attributes do |blackwhite| %>
<%= blackwhite.select :newpages , options_for_select((1..(#print.number_of_images_entry.to_i)).to_a), :multiple => true, :size => #print.number_of_images_entry.to_i %>
<% end %>
<% end %>
Edit 1:
It has "multiple" as i wanted to have multiple selections for the pages.
blackwhite.rb model:
class Blackwhite < ActiveRecord::Base
attr_accessible :print_id
serialize :newpages
belongs_to :print
end
print.rb model:
class Print < ActiveRecord::Base
has_many :blackwhites
belongs_to :user
accepts_nested_attributes_for :blackwhites, :allow_destroy => true
...
...
end
Update 2:
I have watched railscasts and had modified my nested forms as below:
<%= f.fields_for :blackwhites do |blackwhite| %>
<% render 'blackwhites', f: blackwhite %>
<% end %>
in partial _blackwhites.html.erb:
<%= f.select :newpages , (1..(#print.number_of_images_entry)), { :prompt => "0" }, :multiple => true, :size => #print.number_of_images_entry ) %>
and my select fields is no longer appearing.
Your render is not printed because you forgot the equal sign.
<%= render 'blackwhites', f: blackwhite %>

Using has_one with condition to find related object, but how do you create the related object?

I have an Account model with the following :
has_one :primary_user, :class_name => "User", :conditions => "role = 'primary_user'"
So #account.primary_user looks for a user with a role of primary_user.
When creating a new account, I want to be able to create a new primary_user. What is the "Rails way" to do that ?
Do I need to create a primary_user= method?
Here is my create form ..
<%= semantic_form_for #account do |f| %>
<%= f.input :account_name %>
<%= f.semantic_fields_for :primary_user do |user| %>
<%= user.input :email %>
<% end %>
<% end %>
If I submit this form I get
ActiveRecord::AssociationTypeMismatch (User(#2159789500) expected, got ActiveSupport::HashWithIndifferentAccess(#2159703820)):
Thanks
Little cleaner:
has_one :primary_user, :class_name => "User", :conditions => { :role => "primary_user" }
Then straight solution:
<%= semantic_form_for #account do |f| %>
<%= f.input :account_name %>
<%= f.semantic_fields_for :primary_user, primary_user || build_primary_user do |user| %>
<%= user.input :email %>
<% end %>
<% end %>
But I suggest you to add this into new action in controller
#account.build_primary_user
This builds on top of what #fl00r says in his post... I agree the proper place to make build the objects is in the controller. In the new action on your AccountsController, you just need to add a line like this:
#account.primary_user ||= #account.build_primary_user
So your new action would look something like this (plus anything extra you added):
def new
#account = Account.new
#account.primary_user ||= #account.build_primary_user
end
Then your code for the create form should work as is.

accepts_nested_attributes and validates_uniqueness_of

The central problem: How do you merge attribute collections by a key during mass assignment from a nested form.
The details: I am using the following models:
class Location < ActiveRecord::Base
has_many :containers,
:dependent => :destroy,
:order => "container_type ASC"
validates_associated :containers
accepts_nested_attributes_for :containers,
:allow_destroy => true,
:reject_if => proc {|attributes| attributes["container_count"].blank? }
end
class Container < ActiveRecord::Base
belongs_to :location, :touch => true
validates_presence_of :container_type
validates_uniqueness_of :container_type, :scope => :location_id
validates_numericality_of :container_count,
:greater_than => 0,
:only_integer => true
end
So there is a constraint of having only one container type per location. The following views render the location and associated containers:
admin/containers/_index.html.erb
<% remote_form_for [:admin, setup_containers(#location)] do |f| -%>
<% f.fields_for :containers do |container_form| -%>
<%= render "admin/containers/form", :object => container_form %>
<% end -%>
<%= f.submit "Speichern" %>
<% end -%>
admin/containers/_form.html.erb
<% div_for form.object do -%>
<span class="label">
<%- if form.object.new_record? -%>
<%= form.select :container_type, { "Type1" => 1, "Type2" => 2, ... } %>
<%- else -%>
<%= form.label :container_count, "#{form.object.name}-Container" %>
<%= form.hidden_field :container_type %>
<%- end -%>
</span>
<span class="count"><%= form.text_field :container_count %></span>
<%- unless form.object.new_record? -%>
<span class="option"><%= form.check_box :_destroy %> Löschen?</span>
<%- end -%>
<% end -%>
module Admin::ContainersHelper
def setup_containers(location)
return location if location.containers.any? {|l| l.new_record? }
returning location do |l|
all_container_types = [1, 2, ...]
used_container_types = l.containers.try(:collect, &:container_type) || []
next_container_type = (all_container_types - used_container_types).first
l.containers.build :container_type => next_container_type if next_container_type
end
end
Essentially, the helper adds an new container to the collections except all types have already been associated or there is already a new container in the collection. This container is preinitialized to the first not-yet-defined container type. This works out pretty well so far. Adding containers works. Deleting containers works.
The problem is: I want to achieve that choosing and adding a container type which is already in the collection should sum up their counts (instead it would violate the unique constraint). I'm not sure what would be the best way without implementing/reinventing the complete accepts_nested_attributes_for magic - actually I wanted to reduce - not increase - code and complexity by using that.

Resources