Multiple nested form in a HABTM relation - ruby-on-rails

I'm fighting with this bug for the past few hours and I can't make sense of it and my researches didn't give an answer.
It is a basic HABTM relationship. Inputs HABTM Visualizations, and I have a cross table InputsVisualizations that has some attributes of its own.
= form_for(#visualization) do |f|
= f.input :title
= f.fields_for :inputs_visualizations do |iv|
= iv.input :color
= iv.fields_for :input do |i|
= i.input :title
= f.button :submit, "Save"
class Input < ActiveRecord::Base
# Associations ------------------
has_many :inputs_visualizations, dependent: :destroy, order: "inputs_visualizations.order ASC"
has_many :visualizations, through: :inputs_visualizations
# Attributes --------------------
attr_accessible :title, :unit
end
class InputsVisualization < ActiveRecord::Base
# Associations ------------------
belongs_to :input
belongs_to :visualization
# Attributes --------------------
attr_accessible :input_id, :visualization_id, :color, :input_attributes
accepts_nested_attributes_for :input, :reject_if => lambda { |i| i[:title].blank? }, :allow_destroy => true
end
class Visualization < ActiveRecord::Base
# Associations ------------------
has_many :inputs_visualizations, dependent: :destroy, order: "inputs_visualizations.order ASC"
has_many :inputs, through: :inputs_visualizations, order: "inputs_visualizations.order ASC"
# Attributes --------------------
attr_accessible :title, :inputs_visualizations_attributes
accepts_nested_attributes_for :inputs_visualizations, :reject_if => lambda { |a| a[:input_id].blank? }, :allow_destroy => true
end
I need a form for Visualizations that let me manage both InputsVisualizations and Inputs. As you can see in my form, there are two nested fields_for.
Case 1:
I create a nested InputsVisualization with a nested Input (both are new_record). I save the form, they both are created. Cool!
Case 2:
From the same form, I update an Input (existing record). I save, nothing is updated even though the attributes are properly passed to the controller.
I read that nested_attributes don't work with belongs_to relationship, though it created it just fine. Why doesn't it update afterwards?
Thanks

The :reject_if condition on this line looks for an :input_id but that value is not included in the form. So this could prevent the update from going through.
accepts_nested_attributes_for :inputs_visualizations, :reject_if => lambda { |a| a[:input_id].blank? }, :allow_destroy => true

Related

ActiveAdmin: Add association on object creation

When creating a new object and connecting it with existing (has_many :through) resources I need to:
Save the new object first
Edit this newly created object again to add connections to the nested resources.
Cumbersome! It seems ActiveAdmin tries to create the association first, then the main object. Is it somehow possible to do this object creation + associating nested resources in one go?
Is case a more concrete example is needed, here is an example of my data model and ActiveAdmin setup:
Person < ActiveRecord::Base
has_many :organizations, through: :person_organizations
end
Organization < ActiveRecord::Base
has_many :people, through: :person_organizations
end
PersonOrganization < ActiveRecord::Base
belongs_to :person
belongs_to :organization
validates :person, presence: true
validates :organization, presence: true
end
form do |f|
f.inputs do
f.input :name
end
f.inputs 'Organizations' do
f.has_many :person_organizations, do |connection_f|
connection_f.input :organization, as: :select,
collection: Organization.select[:id, :name],
member_label: proc { |org| org.name }
end
end
end
You have to add
accepts_nested_attributes_for :organizations, allow_destroy: true
and if you haven't also the
has_many :person_organizations
in your Person model, and you can place
f.input :organizations, :multiple => true
in your form. Also make sure you permit the correct params in your activadmin register block. In this case it would be
permit_params :name, :organization_ids => []
Read carefully: https://activeadmin.info/5-forms.html#nested-resources and https://activeadmin.info/2-resource-customization.html#setting-up-strong-parameters
I like to decorate multiple select inputs with the select2 javascript library. Let me know if something does not work out.

How to create an object if it does not exist in a nested form in Rails 6?

I need to create a Training, then add Attendances with nested form fields (cocoon) in a Rails 6 app. Here is the situation:
The models are the following:
Training model
class Training < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :people, through: :attendances
accepts_nested_attributes_for :attendances, reject_if: :all_blank, allow_destroy: true
end
Attendance model
class Attendance < ApplicationRecord
belongs_to :training
belongs_to :person
end
Person model
class Person < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :trainings, through: :attendances
end
If a Person exists in the database, then the user selects the person's name from a dropdown select field.
If the Person does not exist, then the user must enter the person's information manually by entering the name and age of the person.
How do I set up the controllers and form to allow a user to manually enter the person's name and age, and then automatically create that person and the attendance in the nested form fields when the training is saved?
The key is having an id field for the nested attributes. Rails's behavour is to update an existing record if the id is present, or to create a new record if the id is blank.
That, essentially, is all you need to do. Enter the person's name and age but leave the id empty, and a new record will be created.
I strongly recommend the cocoon gem https://github.com/nathanvda/cocoon which will give you a link_to_add_association helper for your training form that automatically lets you open a new, empty person form. When implemented you'll call the helper using something like this
<%= link_to_add_association 'add attendee', f, :people %>
You can ignore that the person is associated to the training by a join table... rails will create the join record automatically when you save the new person.
I used the link from the comment above: https://github.com/nathanvda/cocoon/wiki/A-guide-to-doing-nested-model-forms#the-look-up-or-create-belongs_to
And I used the last example in the wiki.
Here is how my final version of the MVC turned out for this feature:
The models
class Person < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :trainings, through: :attendances
end
class Attendance < ApplicationRecord
belongs_to :training
belongs_to :person
accepts_nested_attributes_for :person, :reject_if => :all_blank
end
class Training < ApplicationRecord
has_many :attendances
has_many :people, through: :attendances
accepts_nested_attributes_for :attendances, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :people
end
The trainings controller
class TrainingsController < ApplicationController
put your normal controller actions here and then in
training_params put this:
def training_params
params.require(:training).permit(
attendances_attributes: [:id, :_destroy, :person_id, person_attributes: [:id, :_destroy, :name, :age, :program_id]]
)
end
end
Add this to the trainings/_form
.row
.col.s12
.pt-5
%h6
Attendance
#people
= f.fields_for :attendances do |attendance|
= render 'attendance_fields', :f => attendance
.people_links
= link_to_add_association 'Add', f, :attendances
Add this to trainings/_attendance_fields
.nested-fields.attendance-person-fields
.field
.person_from_list
= f.select(:person_id, Person.all.map { |p| [p.name, p.id] }, { include_blank: true }, { :class => 'chosen-select' })
= link_to_add_association 'Or create', f, :person, data: {disable_with: "creating person"}
Finally in trainings/_person_fields
.nested-fields
.input-field
= f.text_field :name
= f.label :name
.input-field
= f.number_field :age
= f.label :age
I added coffee script :( in: person.coffee
$('#people').on('cocoon:before-insert', (e, attendance_to_be_added) ->
attendance_to_be_added.fadeIn 'slow'
return
)
$('#people a.add_fields').data('association-insertion-position', 'before').data 'association-insertion-node', 'this'
$('#people').on 'cocoon:after-insert', ->
$('.attendance-person-fields a.add_fields').data('association-insertion-position', 'before').data 'association-insertion-node', 'this'
$('.attendance-person-fields').on 'cocoon:after-insert', ->
$(this).children('.person_from_list').remove()
$(this).children('a.add_fields').hide()
return
return

ActiveAdmin Form Multiple Nested HMT relationships with has_many

i'm new to ActiveAdmin and Rails and i struggle on something to build up my ActiveAdmin interface.
Consider the following models :
class PageType < ActiveRecord::Base
has_many :fields, class_name: 'PageField'
accepts_nested_attributes_for :fields, allow_destroy: true
end
class PageField < ActiveRecord::Base
belongs_to :page_type
has_many :page_has_fields
has_many :pages, through: :page_has_fields
accepts_nested_attributes_for :page_has_fields, allow_destroy: true
end
class PageHasField < ActiveRecord::Base
belongs_to :page
belongs_to :page_field
end
class Page < ActiveRecord::Base
belongs_to :page_type
has_many :page_has_fields, dependent: :delete_all
has_many :page_fields, through: :page_has_fields
accepts_nested_attributes_for :page_fields, allow_destroy: true
end
In Active Admin I want to create some page templates to handle "static" pages. And in each of the pages, I want to update the content of each fields related to the templates page.
Thus far, what I did worked with this code :
ActiveAdmin.register Page do
permit_params :name, :page_type_id, :page_id,
:page_fields_attributes => [:id, :name, :field_type, :page_id,
:page_has_fields_attributes => [:id, :content, :page_id]
]
form do |f|
f.inputs
f.has_many :page_fields, heading: false, new_record: false do |g|
g.inputs :name, :required
g.has_many :page_has_fields, new_record: false do |h|
h.input :content if h.object.page_id == f.object.id
end
end
f.actions
end
end
But the second has_many seems really wrong to me, and i'm sure there are a better solution to this problem.
If i don't go with the "if", inputs are created for the right fields, but for every single page.
Is there a way to specify an ID or a parameter in has_many ? Or a better tag to handle situation like this ?
Thanks
Try changing your setup to something more like this
ActiveAdmin.register Page do
...
form do |f|
f.inputs do
f.input :some_column
f.input :some_other_column
f.input :page_fields, as: :check_boxes, checked: PageField.all.map(&:name)
f.input :page_has_fields, as: :check_boxes, checked: PageField.all.map(&:content)
end
f.actions
end
end

Nested whitelisted attributes still unpermitted

I have three models: event, event_user, event_users_day.
event accepts nested attributes event_user which accepts event_users_day as a nested attributes as well.
class Event < ActiveRecord::Base
has_many :event_users, :dependent => :destroy, :inverse_of => :event
accepts_nested_attributes_for :event_users, :allow_destroy => true
end
class EventUser < ActiveRecord::Base
belongs_to :event, :inverse_of => :event_users
has_many :event_users_days, :dependent => :delete_all
accepts_nested_attributes_for :event_users_days, :allow_destroy => true
end
class EventUsersDay < ActiveRecord::Base
belongs_to :event_users, inverse_of: :event_users_days
validates :event_users, :presence => true
end
The simple nested form is pretty straight forward:
= simple_nested_form_for :event_users do |f|
= f.fields_for :event_users_days do |day|
= day.input :event_day_id, as: :check_boxes, collection: #daygroups
= f.submit :class => "btn btn-success"
In my controller event_user and the attributes for event_users_days are whitelisted:
#event_user = EventUser.new(params.permit(:event_id), params[:event_users].permit(:id, event_users_days_attributes: [:id, :event_day_id]))
But when I save it only the EventUser is saved as the server tells me that event_users_days is not permitted:
Unpermitted parameter: event_users_days
Any ideas of what am I doing wrong?
The Unpermitted parameter error is being literal, so your form is generating a event_users_days parameter instead of the expected event_users_days_attributes parameter, which is being rightly rejected by Rails.
I haven't used nested_form in a long time, and if you're using Rails 4 then I'm not sure that's going to be the best pick (and it's not necessary), but even so I think the problem is that you're using :event_users instead of #event_users - but generally I'd recommend switching to simple_form unless you're in an old Rails (and if you are then you should specify that when asking questions on SO).

Nested form in Active Admin

I need help!
I have 2 models for a Survey:
class Poll < ActiveRecord::Base
has_many :poll_questions, :dependent => :destroy
accepts_nested_attributes_for :poll_questions, :reject_if => lambda { |a| a[:text].blank? }, :allow_destroy => true
end
There is model for questions as follows: (it seems these assocciations are correct)
class PollQuestion < ActiveRecord::Base
belongs_to :poll
has_many :poll_answers, :dependent => :destroy
accepts_nested_attributes_for :poll_answers, :reject_if => lambda { |a| a[:text].blank? }, :allow_destroy => true
end
In addition in active Admin:
ActiveAdmin.register Poll do
form do |f|
f.inputs "Main poll" do
f.input :title
f.input :description
end
f.inputs do
f.has_many :poll_questions do |question|
question.input :text
end
end
f.buttons
end
end
It has a beautiful form that doesnt create an actual question object! why?
I've tried my best to solve the problem, but I've failed.
It's probably because you've got a double level for accepts_nested_attributes_for. Why not create a new model for Poll responses which has many Poll answers?
You would then set up an accepts_nested_attributes_for :poll_answers within the PollResponse class.
Then you can not only sort your form problems out, but also track who answered the poll (potentially) and when the poll response was created. The PollResponses model would also have to belong to Polls to differentiate which Poll was being answered.
Try by creating object,
f.has_many :poll_questions, PollQuestion.new do |question|
question.input :text
end

Resources